Issue 1979: Samsung kernel: Heap OOB-write in /dev/tsmux

这是一个tsmux设备的oob_write漏洞,漏洞代码如下

// pat_len, pmt_len and pcr_len are all attacker controlled signed 32-bit integers:

    if (ctx->psi_info.pat_len < 0 || ctx->psi_info.pmt_len < 0 || ctx->psi_info.pcr_len < 0)  // <-- (A)
        psi_validation = 0;
    psi_len = ctx->psi_info.pat_len + ctx->psi_info.pmt_len + ctx->psi_info.pcr_len;          // <-- (B)
    if (psi_len >= TSMUX_PSI_SIZE)                                                            // <-- (C)
        psi_validation = 0;

    if (m2m_job->pkt_ctrl.psi_en && psi_validation) {
        /* PAT CC should be set by tsmux device driver */
        psi_data = (char *)ctx->psi_info.psi_data;          // <-- (D)
        psi_data[3] |= ctx->rtp_ts_info.ts_pat_cc;

        ...

        psi_data += ctx->psi_info.pat_len;                  // <-- (E)

        /* PMT CC should be set by tsmux device driver */
        psi_data[3] |= ctx->rtp_ts_info.ts_pmt_cc;          // <-- (F)

pat_len, pmt_len and pcr_len 这三个值都是用户可控的三个有符号整数。

  • (A)这个地方验证了这三个值不能是负数
  • (B)这里将三个值相加赋值给psi_len,psi_len是一个有符号整数,相加可能会导致算数溢出,psi_len就可能会变成负数
  • (C)这里检查psi_len >= TSMUX_PSI_SIZE 因为没有判断psi_len是否小于0,因此,可以绕过这个检查
  • (D)这里初始化psi_data指针
  • (E)这里给psi_data增加一个pat_len的偏移,因为pat_len最大可以设置为0x7fffffff因此,最大可以访问psi_data偏移2G的内存
  • (F)这里通过或运算,可以改写该内存

链接 :https://bugs.chromium.org/p/project-zero/issues/detail?id=1979

Issue 1961: Memory corruption in launchd due to lack of bounds checking parsing XPC message

(因为第一次接触mac跟ios平台的漏洞,所以我还没太搞清楚launchd与xpc的关系,只知道launchd是一个特权进程)
漏洞出现在sub_100020059函数中,它是subsystem 3的处理函数,里面有大量的switch语句以处理不同的消息。

(因为没有mac的虚拟机因此没法调试,找朋友给了发了lanuchd的文件,完全静态分析的,如有错误还请指出)

case 817:
      ........
      data_buf_len = 0LL;
      v7 = (unsigned __int64)"attr";
      data_buf = (void *)xpc_dictionary_get_data(v5, "attr");
      ........
      if ( !(*((_BYTE *)v22 + 1009) & 0x10) )
      ........
      context = data_buf;
      ........
      v321 = sub_100001C39(
                 (__int64)v22,
                 (__int64)context,
                 data_buf_len,
                 (__int64)v10,
                 &v374,
                 (__int64)poly,
                 v120,
                 &tn);

这个case中的data_buf_len跟data_buf未经检查就传递给了下面的函数sub_100001C39,有可能会产生空指针解引用。

sub_100001c39函数分析:

  v15 = v9 - 0xA4;
  v16 = (char *)sub_10000B421(data_buf, v15, *(unsigned int *)(data_buf + 4));

根据gp0分析的文章,这个函数应该是 _xpc_spawnattr_unpack_string。
_xpc_spawnattr_unpack_string 函数:

const char *__fastcall _xpc_spawnattr_unpack_string(char *data_buf, unsigned __int64 data_buf_size, unsigned int controlled_u32)
{
  const char *v3; // r14
  unsigned __int64 v4; // rcx
  const char *result; // rax

  if ( controlled_u32 >= data_buf_size )
    return 0LL;
  v3 = &data_buf[controlled_u32 + 0xAE];
  v4 = controlled_u32 + strlen(v3) + 1;
  result = 0LL;
  if ( v4 <= data_buf_size )
    result = v3;
  return result;
}

这个函数首先比较了 data_buf_size跟data_buf[1]的大小,如果data_buf[1] < data_buf_size的话,就以data_buf[1]+0xae 为data_buf的索引。但是如果 data_buf_size < data[1] + 0xae 的话,就可能会导致索引越界。继续往下分析

  /*v10 = data_buf*/
  if ( *(_DWORD *)(v10 + 16) )
  {
    v18 = launchd_malloc(8LL * *(unsigned int *)(v10 + 16));
    v19 = v18;
    if ( !v18 )
      goto LABEL_32;
    if ( !sub_10000B460(v10, v15, *(unsigned int *)(v10 + 20), v18, *(unsigned int *)(v10 + 16)) )
    {
      *a8 = 22;
      v36 = v19;
LABEL_35:
      free(v36);
      goto LABEL_36;
    }

这里以 8LL (unsigned int )(v10 + 16) 作为size执行malloc,分配了一段堆内存。然后又将malloc返回的地址跟(unsigned int *)(data_buf+16)作为第4,5个参数,传递给函数 sub_10000B460(_xpc_spawnattr_unpack_strings)。

char *__fastcall _xpc_spawnattr_unpack_strings(__int64 data_buf,
                                               unsigned __int64 data_buf_size,
                                               unsigned int controlled_u32_at_plus_0x14,
                                               char **malloced_outbuf,
                                               unsigned __int64 controlled_u32_at_plus_0x10)
{
  char *v7; // rbx
  __int64 v8; // r15

  if ( controlled_u32_at_plus_0x14 < data_buf_size )
  {
    if ( !controlled_u32_at_plus_0x10 )
      return *malloced_outbuf;
    v7 = (char *)(data_buf + controlled_u32_at_plus_0x14 + 0xAE);
    v8 = 0LL;
    while ( 1 )
    {
      malloced_outbuf[v8] = v7;
      v7 += strlen(v7) + 1;
      if ( (unsigned __int64)v7 > data_buf + data_buf_size + 0xAE )
        break;
      if ( ++v8 >= controlled_u32_at_plus_0x10 )
        return *malloced_outbuf;
    }
  }
  return 0LL;
}

从上面的代码中可以看出来,malloced_outbuf实际上保存了一些字符串指针,它的大小是(8* data_buf[4]),然后通过++v8 >= controlled_u32_at_plus_0x10 来保证不会在保存指针的时候越界,这个流程是没问题的,但是问题在于这一系列代码没有对data_buf_size是否大于0x14做验证,如果我们data只有0x10,那么controlled_u32_at_plus_0x10,controlled_u32_at_plus_0x14就会不可控,gp0给的分析中指出这里是可以通过条件竞争进行利用,因为我没办法进行调试,对mac下对分配方式也不熟悉,因此只能粗略分析一下。因为free chunk的前16个字节分别是prv,next指针,freelist的第一个chunk的prv为0,首先利用堆排布,将xpc data分配的chunk分配到freelist第一个对象前面,这样controlled_u32_at_plus_0x10就等于0,因为malloc(0) = malloc(0x10),,然后通过申请freelist的第一个chunk,再释放,该chunk的prv字段成为一个有效的指针,因此就可以多次循环导致堆溢出。

preView