Parallels Desktop最新虚拟机逃逸漏洞分析(CVE-2020-8871) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Parallels Desktop最新虚拟机逃逸漏洞分析(CVE-2020-8871)

h1apwn 漏洞 2020-05-28 09:25:00
682425
收藏

导语:Mac的Parallels Desktop for Mac是macOS最受欢迎的虚拟化程序之一,但是关于此产品的公开漏洞研究很少。

Mac的Parallels Desktop for Mac是macOS最受欢迎的虚拟化程序之一,但是关于此产品的公开漏洞研究很少。去年11月,Reno Robert(@renorobertr)向ZDI报告了Parallels中的多个漏洞,其中一个漏洞允许guest OS上的本地用户提升特权并在主机上执行代码。该漏洞于5月用15.1.3版本进行了修补,并分配编号CVE-2020-8871(ZDI-20-292)。这篇文章深入研究了该漏洞以及Parallels对其进行的代码更改以修复该漏洞。

0x01  初步分析

以下所有分析均基于15.1.2版。经过测试,guest 虚拟机配置了默认选项。

原始漏洞报告很简短,并且通过简单的Fuzzing就可以找到。以下是POC中的相关代码:

   while (1) { 
     port = random_range(0x3C4, 0x3C5+1); 
     value = random_range(0, 0xFFFF+1); 
     outw(value, port); 
   }

基本上,这会随机且无限地将byte写入I / O端口0x3C4和0x3C5。如果在受影响的Parallels版本上运行POC,它将使prl_vm_app主机OS上的进程崩溃。系统上的每个虚拟机都由一个单独的prl_vm_app进程表示。

 Process 619 stopped 
 * thread #31, name = 'QThread', stop reason = EXC_BAD_ACCESS (code=2, address=0x158d28000) 
     frame #0: 0x0000000108c7a082 prl_vm_app`___lldb_unnamed_symbol5076$$prl_vm_app + 738 
 prl_vm_app`___lldb_unnamed_symbol5076$$prl_vm_app: 
 ->  0x108c7a082 : mov    dword ptr [rsi], ecx 
     0x108c7a084 : cmp    r12d, 0x2 
     0x108c7a088 : jb     0x108c7a0a0               ;  
     0x108c7a08a : mov    dword ptr [rsi + 0x4], ecx 
 Target 0: (prl_vm_app) stopped. 
 (lldb) bt 
 * thread #31, name = 'QThread', stop reason = EXC_BAD_ACCESS (code=2, address=0x158d28000) 
   * frame #0: 0x0000000108c7a082 prl_vm_app`___lldb_unnamed_symbol5076$$prl_vm_app + 738 
     frame #1: 0x0000000108c7ac8b prl_vm_app`___lldb_unnamed_symbol5078$$prl_vm_app + 907 
     frame #2: 0x0000000108c7dd52 prl_vm_app`___lldb_unnamed_symbol5093$$prl_vm_app + 1442 
     frame #3: 0x0000000108ce66dc prl_vm_app`___lldb_unnamed_symbol6282$$prl_vm_app + 636 
     frame #4: 0x0000000108c77bfc prl_vm_app`___lldb_unnamed_symbol5063$$prl_vm_app + 1468 
     frame #5: 0x0000000108c7762c prl_vm_app`___lldb_unnamed_symbol5062$$prl_vm_app + 28 
     frame #6: 0x000000010b91c153 QtCore`___lldb_unnamed_symbol228$$QtCore + 323 
     frame #7: 0x00007fff6879bd76 libsystem_pthread.dylib`_pthread_start + 125 
     frame #8: 0x00007fff687985d7 libsystem_pthread.dylib`thread_start + 15 
 
 (lldb)

通过一些研究,我们发现0x3C4和0x3C5分别是VGA序列发生器索引寄存器和序列发生器数据寄存器。乍一看,似乎VGA设备中存在一个越界(OOB)写漏洞。如前所述,POC由Fuzzing触发,原始报告未提供详细分析。

 http://www.scs.stanford.edu/17wi-cs140/pintos/specs/freevga/vga/seqreg.htm

0x02 根本原因

崩溃是在sub_100185DA0函数中。相关部分进行了简化并注释如下。

 char __fastcall sub_100185DA0(__int64 a1, unsigned int a2, unsigned int a3) 
 {
 //... 
   vga_context = a1; 
   v12 = 0; 
   v13 = 0; 
 //... 
     while ( 1 ) 
     { 
 //... 
       w = (_DWORD *)(vga_context->w); 
 //... 
           dst = (unsigned int *)((_QWORD *)(vga_context->buf) + 4LL * v12 * w); 
           v24 = 0; 
           do 
           { 
             v27 = 8; 
             do 
             { 
 //... 
               v31 = (_DWORD *)((_DWORD *)(vga_context->array[ 4LL * ((_BYTE *)v29) ]) | 0xFF000000); 
               *dst = v31;                       // crash here 
               ++dst; 
               --v27; 
             } 
             while ( v27 ); 
             v24 += 8; 
             v11 = (_DWORD *)(vga_context->w); 
           } 
           while ( v4 * v24 < v11 );             // v4 = 1 
 //... 
       } 
       v12 = v3 * ++v13; 
       if ( v3 * v13 >= (_DWORD *)(vga_context->h) ) // v3 = 1 
         break; 
 ... 
     } 
 //... 
 
 }

该vga_context结构是在VGA设备初始化期间分配的。它保留VGA设备的状态和变量,该函数尝试vga_context->buf通过三个循环顺序写入缓冲区。总长度以vga_context->h * vga_context->w * sizeof(DWORD)字节为单位。然后,它执行OOB写操作,并由于无效长度而在循环内崩溃。

我们研究的第一步是确定vga_context->buf缓冲区内容的来源。

 mapped file  00000001539e1000-00000001579e1000 [ 64.0M 47.9M 0K 0K] rw-/rwx SM=ALI

这个相当大的64MB缓冲区是一个屏幕缓冲区,它是通过guest VM配置(硬件->图形->内存)配置的。这似乎说明,vga_context->h和vga_context->w是为guest VM屏幕分辨率的高度和宽度。

接下来,我们需要确定vga_context->h和vga_context->w缓冲区内容的来源。我们可以从我们的调试这个答案,发现vga_state在sub_100184F90。

 char __usercall sub_100184F90@(int *a1@, __int64 a2@, _DWORD *a3@, unsigned int a4@) 
 { 
   //... 
   vga_state = (_QWORD *)(vga_context->vga_state); 
   v6 = *(_DWORD *)(vga_state->flaggg); 
   if ( v6 ) 
   { 
     width = (unsigned __int16 *)(vga_state->w); 
     height = (unsigned __int16 *)(vga_state->h); 
     // they will save to vba_context later 
   //... 
   }

但是该vga_state对象的来源是什么?

 shared memory 000000011150e000-0000000111514000 [24K 24K 24K 0K] rw-/rwx SM=SHM

我们发现它是共享内存。在这种情况下,它在主机ring0和主机ring3之间共享。它由ring0中的VGA I / O端口处理程序更新,稍后,ring3视频工作线程(位于中sub_100183610)将使用它。

 __int64 __fastcall VgaOutPortFunc(__int16 port, unsigned int cb, unsigned __int64 a3, void *val, void *vga_state, __int64 a6) 
 { 
   v11 = *(_DWORD *)val; 
   v8 = *(_BYTE*)val; 
 //... 
   switch ( (unsigned __int16)(port - 0x3B4) ) 
   { 
 //... 
     case 0x10u:                                 // 0x3c4 
       vga_state->sr_index = v8; 
       return v7; 
     case 0x11u:                                 // 0x3c5 
       switch ( vga_state->sr_index + 95 ) 
       { 
 //... 
         case 9: 
           (_WORD *)vga_state->w = v11; 
           vga_state->sr_index = 0xABu; 
           return v7; 
         case 10: 
           (_WORD *)vga_state->h = v11; 
           vga_state->sr_index = 0xACu; 
           return v7; 
 //... 
         case 13: 
           if ( v11 & 1 ) 
           { 
             (_DWORD *)vga_state->flag8 = 1; 
           } 
           else 
           { 
             (_DWORD *)vga_state->flag8 = 0; 
           } 
 //... 
       } 
 //... 
     case 0x15u:                                 // 0x3c9 
       LOBYTE(i) = vga_state->i; 
       vga_state->i = (_BYTE)i + 1; 
       if ( (_BYTE)i == 2 ) 
       { 
         v19 = vga_state->index2; 
         vga_state->array[4 * v19] = 4 * v8; 
         vga_state->i = 0; 
         vga_state->index2 = (_BYTE*)(v19 + 1); 
       } 
       else if ( (_BYTE)i == 1 ) 
       { 
         *((_BYTE*)vga_state->array[4 * vga_state->index2 + 1]) = 4 * v8; 
       } 
       else if ( (_BYTE)i == 0) 
       { 
         *((_BYTE*)vga_state->array[4 * vga_state->index2 + 2]) = 4 * v8; 
       } 
 //... 
       return v7; 
 //... 
 }

根据上面的伪代码,端口0x3C4充当选择器,以控制端口0x3C5中的操作。端口0x3C5的功能之一是可以将任意16位值设置为vga_state->h和vga_state->w。当ring3视频工作线程获取新的屏幕高度和宽度时,它将尝试更新整个屏幕缓冲区(vga_context->buf)。但是,它不会验证导致屏幕缓冲区溢出的新高度和宽度。

另外,溢出的长度是可控制的。溢出的值可通过端口0x3C9进行部分控制(请参阅参考资料vga_context->array)。结果,我们确定它可能是可利用的。

0x03 补丁分析

补丁发布后,我在15.1.2版和15.1.3版之间进行了一些二进制比较,以确定他们如何选择修复此漏洞。通过仔细检查diff,补丁对sub_100185DA0的调用所做的更改很小。

 __int64 __usercall sub_100186900@(__int64 vga_context@, unsigned int a2@) 
 { 
 //... 
   sub_100184F90((int *)&v29, vga_context, &v28, a2);  // explained above 
   vga_state = (_QWORD *)vga_context->vga_state); 
 //... 
   if ( *(_BYTE *)(vga_context->flaggg) )      // after patch 
 //if ( (_DWORD *)(vga_state->flaggg) )        // before patch 
   { 
 //... 
   } 
   else if ( *(_DWORD *)(vga_state + 15828) )  // looks like always 1 
   { 
 //... 
       sub_100185DA0(vga_context, v28, v29);   // trigger OOB write 
 //... 
   } 
 //... 
 }

if分支之一已更改。补丁程序flaggg已从vga_state移至vga_context。

如我们所见sub_100184F90,flaggg必须是TRUE,vga_state从中获取受控的高度和宽度。但是,flaggg必须具有FALSE崩溃功能。这两个约束是矛盾的。

我们如何满足这些约束?

正如我们先前在查看根本原因时所说的那样,vga_state是ring0和ring3之间的共享内存。该flaggg功能可以通过端口0x3C5进行配置。因此,可以flaggg在这两个约束之间在ring3视频工作线程中翻转并进行两次提取。

补丁实际上所做的是移动flaggg的vga_state到vga_context。由于vga_context在ring3中是堆分配,因此不易受到两次提取的攻击,因此可以改善这种情况。因此,它将永远不会触发OOB写入的路径。

0x04 分析总结

此提交提供了一个很好的示例,说明了如何通过工作流程以及对Parallels Desktop中的虚拟设备进行漏洞原因分析。尽管供应商将补丁列为“低严重性”,但考虑到总体CVSS评分和从guest 升级到主机的机会,应认为该补丁的严重性为“重要”,并尽快将其应用。我们在Parallels Desktop中没有看到很多提交给该程序的bug,但是也许这篇文章会让更多的人看到。如果你也发现了一些漏洞,我们对此非常感兴趣。

本文翻译自:https://www.zerodayinitiative.com/blog/2020/5/20/cve-2020-8871-privilege-escalation-in-parallels-desktop-via-vga-device如若转载,请注明原文地址
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务