win32k.sys驱动CreateSurfacePa的本地提权漏洞分析(CVE-2019-1362)(上)
2019-12-22 11:37:55 Author: www.4hou.com(查看原文) 阅读量:405 收藏

随着用于安全目的的沙箱程序越来越常见,沙箱转义也变得越来越重要。因此,我们一直在寻找这些类型的漏洞。本文讲的这个漏洞是最新发现的一个Windows 7本地权限升级(LPE)漏洞,目前该漏洞已经被定义为CVE-2019-1362。简单来说,该漏洞就是非权限用户可以利用此漏洞在内核上下文中执行代码并获得系统权限。

漏洞介绍

此漏洞是由于win32k.sys缺少对参数的验证。CreateSurfacePal内核函数。这个函数涉及到处理调色板对象。palette对象是作为可用颜色数组的内核模式对象。它与某些图形输出设备(显示器和打印机)一起使用,这些设备被配置为使用一组预定义的颜色进行操作。

在Windows NT内核的最初设计中,打印机驱动程序是加载到内核中的模块。从Windows Vista开始,微软在架构上做了重大改变:打印机驱动程序将以用户模式运行,而不是作为内核的一部分运行。这个变化是作为一个基本的安全增强而做出的,它带来的安全好处是非常明显的:一旦进入用户模式,打印机驱动程序中的漏洞对安全的影响就会大大降低。

然而,具有讽刺意味的是,这种体系结构的更改对留在内核中的其余图形代码的安全性产生了相反的影响。重构打印机驱动程序并将它们移动到用户空间中,这就创建了一个全新的攻击面,其中包括在内核模式图形代码和现已迁移到用户空间的驱动程序代码之间创建的新接口。曾经安全的内核模式代码现在可以通过各种方式失败,这是由于现在在用户模式下运行的打印机驱动程序代码的影响,而这种模式更容易被篡改。

特别是win32k.sys!CreateSurfacePal内核函数是这个漏洞的主题,用户模式的打印机驱动程序架构第一次允许在用户模式下运行的恶意代码传递一个无效的参数。通常,攻击将通过干扰合法的打印机驱动程序的功能进行。

攻击过程

让我们从连接用户模式打印机驱动程序开始,用户模式打印驱动程序是在系统注册表中注册并导出一些标准化函数的DLL。

例如,默认的“Microsoft XPS文档编写器”驱动程序位于:C:\Windows\system32\spool\DRIVERS\W32X86\3\ mxdwdrv.dll

并导出以下函数:

1.png

当用户使用“Microsoft XPS文档编写器”作为参数调用gdi32.dll!CreateDCA / W API时,将加载mxdwdrv.dll驱动程序。然后进行从内核到用户模式的回调,并调用mxdwdrv.dll!DrvEnableDriver函数:

2.png

在调用之后,pded参数返回一个指向DRVENABLEDATA结构的指针:

3.png

其中c给出了pdrvfn表中的若干项,pdrvfn表由DRVFN记录组成:

4.png

pdrvfn表位于mxdwdr .dll驱动程序内存中的某个位置,每个_DRVFN项描述了实现的驱动程序的一个函数—其中iFunc是winddi.h标头文件中列出的值之一:

5.png

pfn是指向用户模式代码的指针,位于mxdwdr .dll驱动程序的某个位置。

通过覆盖pdrvfn表,我们可以将驱动程序实现的任何函数重定向到我们自己的代码段。

虽然我们使用“Microsoft XPS文档编写器”驱动程序作为示例,但是任何安装在操作系统中的打印驱动程序都可以使用。这个驱动挂钩算法是“算法1”:

1. 使用PRINTER_ENUM_ LOCAL参数调用用户模式winspool.drv!EnumPrintersA / W API以查找任何可用的打印机。对于每台打印机,返回其名称。在我们的示例中,它是“Microsoft XPS文档编写器”。

2. 使用winspool.drv!OpenPrinterA / W和winspool.drv !获取驱动程序DLL的路径,即mxdwdr . DLL的路径。

3. 使用LOAD_WITH_ALTERED_SEARCH_PATH标志调用kernel32.dll!LoadLibraryExA / W来加载驱动程序DLL。 

4. 调用驱动程序的DrvEnableDriver导出函数,以获得pdrvfn表的地址。

5. 使用kernel32!VirtualProtect API使pdrvfn表在内存中可写。

6. 根据需要修改pdrvfn表,你应该保存表的原始内容,以便能够调用原始函数。

7. 调用驱动程序的DrvDisableDriver导出函数,打印驱动程序DLL仍然加载在内存中,处于其补丁状态。

8. 使用在步骤1中获得的打印机名称调用gdi32.dll!CreateDCA / W API。此调用在内部加载打印驱动程序DLL。但是,它已经加载并由我们修补,因此仅DLL的引用计数器会增加。这样,我们强制内核使用内存中修补的打印驱动程序,该驱动程序将所需的驱动程序函数会重定向到我们自己的代码。

为了进一步利用,我们将挂钩打印驱动程序的DrvEnablePDEV函数。为此,我们将修改_DRVFN记录,它具有iFunc == INDEX_DrvEnablePDEV:

6.png

我们的计划是从连接的代码中调用原始的DrvEnablePDEV函数,然后修改pdevcaps和pdi记录中返回的某些字段:

7.1.png

7.2.png

hpalDefault字段是模板调色板的句柄,必须通过在用户模式下调用gdi32.dll!EngCreatePalette API来创建此调色板。默认情况下,此调色板具有256个条目-每个条目都是4字节结构:

8.png

在flGraphicsCaps字段中,我们将设置GCAPS_PALMANAGED标志,因此上面给出的选项板将在“托管”模式下使用,默认情况下不设置此标志。

ulNumColors值给出了上面给出的托管调色板中的许多保留项。默认情况下,它等于20,所以前10和后10个调色板条目是保留的,它们不能被应用程序修改,其余条目(中间部分)可以自由修改。

ulNumPalReg值给出了上面给出的托管调色板中的所有条目的数量,默认值是256。

win32k.sys!CreateSurfacePal中的漏洞

调色板对象在内核模式中由_PALOBJ结构表示,该结构没有公开定义(winddi.h包含一个空注明)。但是,在ReactOS文档中可以找到一些信息(尽管使用了另一个结构名称):

9.png

_PALOBJ结构中有一个ppalThis指针,默认情况下指向该结构本身。调色板项紧跟在内存中的_PALOBJ结构之后。注意,第一个条目仍然属于_PALOBJ结构本身,并被注明为apalColors[1]。pFirstColor字段指向第一个调色板条目。默认情况下,它只指向apalColors[1]字段。

在用户模式下调用gdi32.dll!CreateDCA / W API之后,从内核进行回调以调用我们的挂钩,即用户模式DrvEnablePDEV函数。然后,内核对DrvEnablePDEV调用返回的数据执行各种操作。特别是,如果flGraphicsCaps字段设置了GCAPS_PALMANAGED标志,则在hpalDefault字段中返回的调色板将用作模板。它用于创建其内部副本,该内部副本成为设备选板。这是在win32k.sys!CreateSurfacePal函数中执行的,该函数还使用了我们的ulNumColors和ulNumPalReg值。在新创建的设备选项板中,通过将其peFlags值设置为0x30来初始化选项板条目(将成为保留条目)。

win32k.sys!CreateSurfacePal函数(算法2)的算法为:

1. CreateSurfacePal接受以下参数:

1.1 指向hpalDefault调色板内核内存的指针,即指向内核内存中调色板的_PALOBJ结构的指针。我们将这个指针命名为palSrc。

1.2 ulNumColors(保留条目的数量)

1.3 ulNumPalReg

2. 调用win32k.sys!bCreatePalette以使用我们的palSrc调色板作为模板来创建一个新的调色板。新的调色板将成为一个设备调色板,我们把它命名为palDst。

3. 用0x30 peFlags值初始化palDst中的保留项,伪造的代码如下:

10.png

4. 调用win32k.sys!XEPALOBJ::vCopyEntriesFrom,将所有调色板条目从palDst复制到palSrc。在x64上,这个调用是内联的,所以只调用win32k.sys!memmove。

5. 调用win32k.sys!XEPALOBJ::ulTime,也内联在x64上,将palSrc->的ulTime值复制到:

5.1 palDst – > ulTime字段;

5.2 palDst->ppalThis->ulTime field(仅当palDst->ppalThis != palDst)

在正常情况下,将使用以下参数调用CreateSurfacePal:

1. hpalDefault是一个包含256个条目的调色板句柄;

2. ulNumColors(保留条目的数量)= 20

3. ulNumPalReg = 256

因此,在步骤3中描述的代码在palDst面板的前10个和后10个条目中将peFlags值设置为0x30:

11.png

在步骤3中描述的代码的漏洞是,该算法未针对调色板条目的真实数量(即palSrc-> cEntries)验证ulNumColors参数(指示保留条目的数量)。在用户模式下劫持了打印机驱动程序后,攻击者可以传递超出范围的ulNumColors值,从而导致越界写入内存。

伪造设默认调色板大小为256个条目,攻击者应该传递的ulNumColors值为514:

12.png

正如上面所看到的,通过将PALETTEENTRY记录(它是一个DWORD)的最高字节设置为0x30,可以修改范围下面的一个条目和范围上面的一个条目。

范围下面的一个条目包含一个palDst->ppalThis字段,它允许我们通过将ppalThis指针的最高字节设置为0x30来更改它。

幸运的是,超出范围的一个条目总是未被使用。在为调色板对象分配内存时,将进行如下计算:sizeof(_PALOBJ) + sizeof(PALETTEENTRY) * NumberOfEntries

但是,_palobj结构包含一个条目(apalColors[1]字段),因此总是分配多于所需的条目。

在x86上的利用

在x86上,palDst->ppalThis是一个4字节的值。通过用0x30覆盖它的最高字节,我们将它设置为一个格式为0x30XXXXXX的值,这代表一个用户模式地址。被覆盖的palDst-> ppalThis指针的首次使用是在CreateSurfacePal函数本身中。它用于写入ulTime字段。这是上述算法2的步骤5。

如果我们只想让操作系统崩溃,那么只要确保从0x30000000到0x30ffffff的整个内存是不可访问的就足够了。

实现权限升级

现在,我们将考虑如何利用此漏洞来实现权限升级。当使用没有任何操作的打印机驱动程序时,操作顺序如下:

1. 应用程序代码调用gdi32.dll!CreateDCA/W,它将找到并加载打印机驱动程序DLL。

2. 打印机驱动程序DLL通过调用gdi32.dll!EngCreatePalette创建一个模板调色板。

3. 内核回调到用户模式并调用打印机驱动程序的DrvEnablePDEV函数,此时,DrvEnablePDEV可以指定要使用的模板调色板的句柄。内核将使用这个句柄获得指向调色板内核内存的指针。在CreateSurfacePal中,这将是palSrc。

4. 如果DrvEnablePDEV调用返回GCAPS_PALMANAGED标志,内核将基于模板调色板创建一个驱动程序调色板。在CreateSurfacePal中,这是palDst。在模板调色板palSrc中,内核设置hSelected字段指向新创建的设备调色板:palSrc->hSelected = palDst。

5. 现在执行常规打印操作。

6. 用户调用gdi32.dll!DeleteDC;

7. 内核回调到用户模式并调用打印机驱动程序的DrvDisablePDEV函数。打印机驱动程序DLL通过调用gdi32.dll!EngDeletePalette来删除模板调色板。因为模板调色板在它的hSelected字段中引用设备调色板,所以设备调色板(也称为palDst)会自动删除。

8. 打印驱动程序DLL被卸载;

现在,我们来关注“设备选项板被自动删除”部分。实际上,设备选项板的palDst-> ppalThis字段指向_PALOBJ记录,其用法如下:

1. 访问BaseObject字段,该字段在BaseObject结构的开头包含palDst调色板的句柄。调色板的句柄将传递到win32k.sys!HmgRemoveObject,它要求调色板的引用计数器为1。引用计数器由操作系统内部管理。

2. 成功调用HmgRemoveObject之后,将ppalThis指针传递给win32k.sys!FreeObject调用,该调用又将指针传递给win32k.sys!ExFreePoolWithTag以释放对象。

通过触发该漏洞,我们能够将ppalThis指针重定向到用户模式内存。如果可以在该地址欺骗“足够有效”的_PALOBJ结构,则可以使该用户模式地址传递给ExFreePoolWithTag,这是我们进一步开发的目标。

为了“足够有效”,我们伪造的用户模式_PALOBJ结构必须用0来填充,但也有例外情况,比如:

1. BaseObject字段必须包含其引用计数器等于1的调色板的有效句柄。

2. ppalThis字段必须指向伪造结构的开头,以避免在目标销毁算法中进一步递归。

有两个漏洞需要克服,首先,覆盖的ppalThis指针指向某个0x30XXXXXX位置,但是我们不知道确切的位置。幸运的是,在初始准备阶段,用0来填充从0x30000000到0x30ffffff的整个内存范围就足够了。然后,如算法2的步骤5所述,CreateSurfacePal函数将通过引用已经覆盖的ppalThis指针将非零时间戳记写入ulTime字段。然后,我们可以扫描内存范围中的非零DWORD值,这揭示了我们欺骗的用户模式_PALOBJ结构的确切位置。

第二个漏洞是,伪造的_PALOBJ结构的BaseObject字段必须包含一个句柄,其引用计数器等于1。通常情况下,这应该只是内核创建的设备调色板palDst的句柄,但我们不这样做。不知道此调色板的句柄值。幸运的是,我们可以使用其他任何调色板。然后,漏洞就变成了在哪里获得引用计数器等于1的调色板。有趣的是,可以通过再次使用我们的挂钩打印机驱动程序DLL获得这种调色板。我们可以再次调用gdi32.dll!CreateDCA / W并将新创建的模板选项板传递给内核,这一次无需执行任何其他操作。从CreateDCA / W调用返回后,只要我们不调用gdi32.dll!DeleteDC,我们的模板面板的引用计数器就会等于1。现在,我们可以将此调色板的句柄放置在伪造的_PALOBJ结构的BaseObject字段中。

现在,我们可以将用户模式_PALOBJ结构的地址传递给内核ExFreePoolWithTag调用,这绝对是一种不寻常的情况。在正常情况下,ExFreePoolWithTag只处理内核内存块。通过使用两个伪造的池头(pool header)来包围伪造的用户模式_PALOBJ结构,我们可以欺骗ExFreePoolWithTag内部机制,将一个半控制的值写入一个完全控制的内核内存地址。当然,这正是我们想要做的。

ExFreePoolWithTag函数可用于所谓的池内存块,描述池内存利用的所有细节将远远超出本文的讨论范围。所以,我只会介绍一下重点。下一篇文章,我将介绍更进一步的漏洞攻击。

本文翻译自:https://www.thezdi.com/blog/2019/12/16/local-privilege-escalation-in-win32ksys-through-indexed-color-palettes如若转载,请注明原文地址: https://www.4hou.com/vulnerable/22221.html


文章来源: https://www.4hou.com/vulnerable/22221.html
如有侵权请联系:admin#unsafe.sh