CVE-2021-24093 Windows图形组件远程执行代码漏洞分析

0x00 前言

Windows图形组件DWrite库是用于高质量文本呈现的用户模式动态库,DWrite库存在远程代码执行漏洞。目前已有POC,POC可以实现任意地址写任意内容。

0x01 漏洞信息

漏洞简述

漏洞名称:Windows图形组件远程执行代码漏洞
漏洞编号:CVE-2021-24093
漏洞类型:数组越界
CVSS评分:8.8
基础条件:用户访问网页

组件概述

Microsoft DirectWrite是用于高质量文本呈现的现代Windows API。它的大部分代码位于DWrite.dll用户模式库中。它用作各种广泛使用的桌面程序(例如Windows上的chrome,Firefox和Edge)的字体光栅化程序。

漏洞利用

使用chrome浏览器访问Web页面,渲染引擎进程异常,导致chorme页面崩溃。

漏洞影响

漏洞主要影响Win10的某些版本1909等及Windows Server 2016、2019、2004、20H2等系统。

0x02 漏洞复现

1.环境

Windows 10 1909专业版x64,chrome 86.0.4240.193(64位)

poc.html放在本机,漏洞环境也在本机

2.复现

打开poc.html,选择加载ttf文件

浏览器渲染引擎崩溃

3.定位

(1) 打开Html页面时会启动多个chrome进程,首先需要定位到哪个是渲染引擎进程。

(2)先关闭chrome浏览器(否则会影响定位结果),使用火绒剑来定位渲染引擎进程。

做如下设置

(3)首先先看看运行崩溃的正常捕获情况:

我们看到id为4780的进程发生过崩溃,盲猜目标就是第六个进程,此时我们尝试不加载ttf文件

此时便没有崩溃进程,利用windbg attach这个进程,输入g运行,chrome加载ttf文件,此时便可以断到引发崩溃的位置。

看看当前寄存器r8:

0x04 漏洞分析

基本信息

漏洞文件:DWrite.dll
漏洞函数:fsg_ExecuteGlyph
漏洞对象:TrueType字体中的”maxp”表

背景知识

TrueType字体通常包含在单个TrueType字体文件中,其后缀为.TTF。TrueType中的所有数据都使用big-endian编码,TTF文件中包含了字体的版本号和几个表。

typedef   sturct
{
    char   tag[4];
    ULONG   checkSum;
    ULONG   offset;
    ULONG   length;
}TableEntry;

typedef   struct
{
    Fixed   sfntversion;   //0x10000   for   version   1.0
    USHORT   numTables;
    USHORT   searchRange;
    USHORT   entrySelector;
    USHORT   rangeShift;
    TableEntry   entries[1];//variable   number   of   TableEntry
}TableDirectory;

TableDirectory结构的最后一个字段是可变长度的tableentry结构的数组,安体中的每个表对应其中一项。TrueType字体中的每个表都保存了不同的逻辑信息—–如图元中数据、字符到图元的映射、字距调整信息等等。

head           字体头                                           字体的全局信息
cmap           字符代码到图元的映射               把字符代码映射为图元索引
glyf           图元数据                                       图元轮廓定义以及网格调整指令
maxp           最大需求表                                   字体中所需内存分配情况的汇总数据
mmtx           水平规格                                       图元水平规格
loca           位置表索引                                   把元索引转换为图元的位置
name           命名表                                           版权说明、字体名、字体族名、风格名等等
hmtx           水平布局                                       字体水平布局星系:上高、下高、行间距、最大前进宽度、最小左支撑、最小右支撑
kerm           字距调整表                                   字距调整对的数组
post           PostScript信息                           所有图元的PostScript   FontInfo目录项和PostScript名
PCLT           PCL   5数据                                     HP   PCL   5Printer   Language   的字体信息:字体数、宽度、x高度、风格、记号集等等
OS/2           OS/2和Windows特有的规格         TrueType字体所需的规格集

TrueType字体是一种非常灵活的数据结构,它可以包含可变数目的图元,每个图元可以有不同数目的控制点,甚至还可以有数量可变的图元指令。最大需求表的目的是告知字体栅格器(rasterizer)对内存的需求,以便 在出来字体前分配合适大小的内存。因为性能对字体栅格器非常重要,像MFC的CAarray那样需要频繁进行数据拷贝操作的动态增长的数据结构不合要求。下面是maxp表的结构。

typedef   struct
{
    Fixed   Version;//0x00010000   for   version   1.0.  4bit
    USHORT   numGlypha;   //Number   of   glyphs   in   the   font   .
    USHORT   maxPoints;   //Max   points   in   noncomposite   glyph   .
    RSHORT   maxContours;   //Max   contours   in   noncomposite   glyph.
    USHORT   maxCompositePoints;//Max   points   in   a   composite   glyph.
    USHORT   maxCompositeContours;   //Max   contours   in   a   composite   glyph.
    USHORT   maxZones;//   1   if   not   use   the   twilight   zone   [Z0],
                                    //or   2   if   so   use   Z0;2   in   most   cases.
    USHORT   max   TwilightPoints   ;/   Maximum   points   used   in   Z0.
    USHORT   maxStorage;   //Number   of   storage   area   locations.
    USHORT   maxFunctionDefs;   //Number   of   FDEFs.
    USHORT   maxStackElements;   //Number   of   depth.
    USHORT   maxSizeOfInstructions;   //Max   byte   count   for   glyph   inst.
    USHORT   maxComponentElements;   //Max   number   top   components   refernced.
    USHORT   maxComponentDepth;       //Max   levels   of   recursion.
}Table_maxp;

详细分析

ttf文件的二进制数据

USHORT == 2 bit
RSHORT == 2 bit

方框的位置是maxp表的tableEntry结构,偏移0x158的是maxp表的内容,numGlypha字段是0x0006 maxPoints字段值为0x0000(距离箭头2偏移0x6),maxContours字段是0x002a,maxCompositePoints字段为0x0003(距离箭头2偏移0xA)。

poc内容:

正常情况下,ttf文件中maxPoints字段值为0x168,maxCompositePoins字段值为0x2352,在poc.ttf文件中将”maxp”结构中maxPoints字段的值改为0,将maxCompositePoins值改为3,当加载并光栅化损坏的”maxp”表的数据时,会导致堆分配缓冲区过小,调用栈如下:

函数DWrite!fsg_ExecuteGlyph崩溃,调用栈如下:

# Child-SP          RetAddr               Call Site
00 000000ff`debfa170 00007fff`c05a3886     DWrite!fsg_ExecuteGlyph+0x772
01 000000ff`debfa2a0 00007fff`c05a35ab     DWrite!fsg_CreateGlyphData+0x12e
02 000000ff`debfa360 00007fff`c05a2d65     DWrite!fsg_GridFit+0xbb
03 000000ff`debfa3f0 00007fff`c05a17fa     DWrite!fs__Contour+0x1c1
04 000000ff`debfa4e0 00007fff`c05a15e7     DWrite!TrueTypeRasterizer::Implementation::RasterizeInternal+0xfa
05 000000ff`debfa520 00007fff`c05a1534     DWrite!TrueTypeRasterizer::Implementation::GetBitmapInternal+0x2f

查看漏洞库版本

0:000> lmm DWrite -v
Unknown option '-'
Browse full module list
start             end                 module name
00007fff`c0520000 00007fff`c081e000   DWrite     (pdb symbols)          C:\ProgramData\Dbg\sym\DWrite.pdb\FCC9572DA72927C3057C3859D67B0DD61\DWrite.pdb
    Loaded symbol image file: C:\Windows\SYSTEM32\DWrite.dll
    Image path: C:\Windows\SYSTEM32\DWrite.dll
    Image name: DWrite.dll
    Browse all global symbols  functions  data
    Image was built with /Brepro flag.
    Timestamp:        793762E4 (This is a reproducible build file hash, not a timestamp)
    CheckSum:         00300C4B
    ImageSize:        002FE000
    File version:     10.0.18362.356
    Product version:  10.0.18362.356
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® Windows® Operating System
        InternalName:     DWrite
        OriginalFilename: DWrite
        ProductVersion:   10.0.18362.356
        FileVersion:      10.0.18362.356 (WinBuild.160101.0800)
        FileDescription:  Microsoft DirectX Typography Services
        LegalCopyright:   © Microsoft Corporation. All rights reserved.

静态分析

用IDA逆向DWrite.dll基本上看不出什么东西…

动态分析

漏洞如何利用

不调试真不知道都在干些什么:断到memset处,第三个参数是0x148

一直运行到崩溃的位置

向无效地址写入内容,自然会引起崩溃,我们注意到在前面的mov r8,qword ptr [rax+8]对r8进行赋值的,我们看看r8地址的内容:

那么猜测一下可能是以下两个问题:
1.由于溢出的原因,导致读取的ttf数据覆盖掉了本来的合法地址
2.由于rsi本身偏移的问题,也就是说rsi的值向后移动了一部分,将后面的ttf的数据作为地址

那么我们就需要关注一下一开始的堆地址以及后面的寄存器中值的变化:

第一个memset

断在rcx赋值时

断在第一个memset处

第一个堆地址:0x000001e22eba6c50,我们发现在memset的时候堆地址是0x000001e22eba71ec(偏移增加了0x59c)

第二个memset

第二个堆地址:0x000001e22eba6c50,我们发现memset的时候地址是0x000001e22eba71c0(偏移增加了0x6c0)

赋值非法地址给r8的时候

rsi的值是:0x000001e22eba7310

TTF中的数据覆盖了[rsi+8],在后面的add word ptr [r8+56],ax指令中,ax其实也是可以通过ttf数据控制

也就是说通过
mov r8, qword ptr [rsi+8]
mov rax, qword ptr [rsi+0A8h]
add word ptr [r8+56h], ax
就可以达到任意代码执行的目的,因为指针和指针的值都可以通过ttf数据控制。

但是我们还是没有解决到底是什么原因触发的这个漏洞

触发漏洞的原因

函数分析:fsg_ExecuteGlyph函数对堆块内部的两个整数数组,对应于x坐标和y坐标进行操作,实际上数组的较小,fsg_ExecuteGlyph函数先调用了两次memset将数组清零,如果字体是一个变量且指定了轴值,还会调用TrueTypeRasterizer::Implementation::ApplyOutlineVariation->GlyphOutlineVariationInterpolator::ApplyVariation将字体中的数据赋值到坐标数组,这样就会破坏到后续的结构成员。

那么我们可以看看这个堆块到底是分配了多大的:

计算内存大小的函数调用链为TrueTypeRasterizer::Implementation::Initialize-> fs_NewSfnt ->fsg_WorkSpaceSetOffsets函数fsg_WorkSpaceSetOffsets内部计算需要申请的内存空间大小并将结果传出到fs_NewSfnt中。

计算所需内存的过程可以通过条件断点的方式来调试,附加到调试器后,可以设置如下断点命令

bp DWrite!TrueTypeRasterizer::Implementation::Initialize “r $t0=$t0+1; .printf "Initialize times:%d\n",@$t0;.echo;gc”

我们看到第13次调用TrueTypeRasterizer::Implementation::Initialize函数之后就会进入崩溃

重启下断点:
bp DWrite!TrueTypeRasterizer::Implementation::Initialize “r $t0=$t0+1; .printf "Initialize times:%d\n",@$t0;.echo;.if(@$t0 == 0x0D){}.else{gc}”

重启在调用fs_NewSfnt前下断点

可以看到申请的内存大小0x6fa4

继续运行:

可以看到申请的内存地址是0000012f8cd56c50

在崩溃函数中下断点,目的是看看操作的空间和分配的空间:

内存起始位置是0000012f8cd56c50,内存块使用地址是0000012f8cd571ec

查看堆块大小以及addr1相对偏移0x59c

我们看看之前赋值非法地址到r8处的值

看到了偏移是6c8,好了这下明白了非法地址的来源了:

第一个堆地址相对于堆块起始位置是0x59c+memset的0x148的初始化>0x6c8

也就是说在这个函数中:

已经将合法地址改为ttf中的数据

0x05 patch

分配堆块更大,x数组和y数组的大小还是0x148字节,ESI对象距离堆块起始地址的距离变大,所以在x数组和y数组的赋值过程中没有覆盖到ESI对象。也就是说addr+0x148<esi对象对应的堆地址

0x06 重要参考

CVE-2021-24093 Windows图形组件远程执行代码漏洞分析


 Previous
java反序列化总结 java反序列化总结
前言本篇文章初步学习java反序列化以及反序列化利用需要掌握的java反射机制,文章内容借鉴多篇前人文章,总结仅供学习和参考 java反序列化入门(一)初识JAVA反序列化漏洞0x0 java序列化与反序列化0x00 简单介绍Java 序列
Next 
House of Kiwi House of Kiwi
0x00 背景这两天调内核的cve实在是被恶心到了,针对问题也去请教了师傅,今天是调不下去了。 在2.29以后由于setcontext中的rdi改为rdx寄存器,需要gadgets将rdi->rdx: 常见的一个操作是劫持mall
2021-04-15
  TOC