标题 简介 类型 公开时间
关联规则 关联知识 关联工具 关联文档 关联抓包
参考1(官网)
参考2
参考3
详情
[SAFE-ID: JIWO-2024-2908]   作者: 大猪 发表于: [2021-07-09]

本文共 [274] 位读者顶过

概述

Windows图形组件DWrite库存在数组越界写漏洞 (CVE-2021-24093),可导致远程代码执行。 [出自:jiwo.org]

当DWrite库解析恶意构造的字体文件时,计算内存分配长度时出现错误,触发越界写,而且字体文件中的数据能够越界写入到任意位置,使攻击者可以做到RCE。

背景知识

TrueType (Free Library)字体通常包含在单个TrueType字体文件中,其文件后缀为.TTF。文件开头是 TableDirectory 结构,TTableDirectory 结构的最后一个字段是可变长度的 TableEntry 结构的数组。TrueType 字体中的所有数据都使用 big-endian 编码。

TrueType字体目录的c语言定义:

typedef sturct{char tag[4];ULONG checkSum;ULONG offset;ULONG length;}TableEntry;typedef struct{Fixed sfntversion; //0x10000 for version 1.0USHORT numTables;USHORT searchRange;USHORT entrySelector;USHORT rangeShift;TableEntry entries[1];//variable number of TableEntry}TableDirectory;

通过 010 Editor 编辑器打开poc.ttf文件,可以清楚的看到 ttf 文件的结构。

1624504411_60d3f85b6af70c9d4a85e.png!small?1624504398742

重点关注 maxp 表中的 maxPoints 字段和 maxCompositePoints,可以看到它们的值分别为0和3,它们相对于maxp表起始地址的偏移值分别为6和10。

复现

获取 PoC 后,将 poc.html 和 poc.ttf 放到同一目录,双击打开 poc.html 文件。

PoC 地址:

https://packetstormsecurity.com/files/161582/Microsoft-DirectWrite-fsg_ExecuteGlyph-Buffer-Overflow.html

1624504448_60d3f88021b85b092a7f3.png!small?1624504434454

1624504456_60d3f888d18bb6fc4cc95.png!small?1624504443366

点击OK,加载 poc.ttf 字体文件。浏览器渲染进程崩溃。

1624504475_60d3f89bd4d7a1b4cb74e.png!small?1624504462159

用 windbg 定位崩溃点。在第2步点击OK之前,将windbg工具attach到6768进程上。

1624504485_60d3f8a5998e8885194fb.png!small?1624504472108

如何确定进程6768是chrome当前渲染进程呢?可以借助procexp64.exe工具。

1624504493_60d3f8ada88a1445d824f.png!small?1624504480396

继续执行,在DWrite!fsg_ExecuteGlyph+0x72c位置触发内存访问违例,代码 “add word ptr [r8+56h],ax” 引用了一个非法地址。

1624504505_60d3f8b9411968e1018b1.png!small?1624504492216

此时的调用栈为:

同时可以使用BrokenType/ttf-otf-dwrite-loader直接加载poc.ttf复现漏洞,崩溃地址不变,调用栈为:

BrokenType/ttf-otf-dwrite-loader的下载地址为:

https://github.com/googleprojectzero/BrokenType/tree/master/ttf-otf-dwrite-loader

漏洞成因

字体加载后会调用 TrueTypeRasterizer::Implementation::Initialize 初始化函数,在 Initialize 内部调用 fs_NewSfnt 函数,fs_NewSfnt 又会调用fsg_WorkSpaceSetOffsets 函数,fsg_WorkSpaceSetOffsets 的作用是计算内存分配长度。查看 fsg_WorkSpaceSetOffsets函数的伪代码如下:

1624504538_60d3f8dadeb6d6dee3fb8.png!small?1624504525386

a1是指向maxp表的指针,v10 = *((WORD *)a1 + 5)取字段maxCompositePoints 的值,*((WORD *)a1 + 3取字段maxPoints,因为maxCompositePoints和maxPoints的偏移分别是10和6,同时a1被强制转化为(_WORD *) 类型,所以伪代码中偏移5和3就对应10和6。

这段代码逻辑是,比较maxPoints和maxCompositePoints的大小,取大值再与数值1比较大小,最后加上数值8后作为第一个参数传入fsg_GetOutlineSizeAndOffsets函数,最后返回一个计算好的数值。

内存分配操作在Initialize函数调用fs_NewSfnt之后不远处,可以看到fsg_WorkSpaceSetOffsets函数返回的数值最终影响内存分配的长度。

1624504550_60d3f8e693d47ac4876d1.png!small?1624504537420

poc.ttf中,maxPoints和maxCompositePoints的值分别是0和3,是畸形数据。

由于fsg_WorkSpaceSetOffsets函数中没有恰当处理,会导致Initialize函数内存分配不足。

当字体渲染时,由于内存分配不足,数据结构fsg_WorkSpaceAddr的内容会覆盖结构体GlyphData。

typedef struct fsg_WorkSpaceAddr{F26Dot6 *              pStack;                     /* Address of stack                  */void *                 pGlyphOutlineBase;      /* Address of Glyph Outline Base     */fnt_ElementType *    pGlyphElement;          /* Address of Glyph Element array    */boolean *              pGlyphDataByteSet;      /* Address of ByteSet array          */void *                 pvGlyphData;                /* Address of GlyphData array        */void *                 pReusableMemoryMarker;  /* Address of reusable memory        */} fsg_WorkSpaceAddr;struct GlyphData{char        acIdent[2];             /* Identifier for GlyphData                         */GlyphData * pSibling;               /* Pointer to siblings                              */GlyphData * pChild;                 /* Pointer to children                              */GlyphData * pParent;                /* Pointer to parent                                */sfac_GHandle hGlyph;                /* Handle for font access                           */GlyphTypes  GlyphType;              /* Type of glyph                                    */uint16      usGlyphIndex;           /* Glyph Index                                      */BBOX        bbox;                   /* Bounding box for glyph                           */uint16      usNonScaledAW;          /* Nonscaled Advance Width                          */int16       sNonScaledLSB;          /* Nonscaled Left Side Bearing                      */uint16      usDepth;                /* Depth of Glyph in composite tree                 */sfac_ComponentTypes MultiplexingIndicator;/* Flag for arguments of composites                */boolean     bRoundXYToGrid;         /* Round composite offsets to grid                  */int16       sXOffset;               /* X offset for composite (if supplied)             */int16       sYOffset;               /* Y offset for composite (if supplied)             */uint16      usAnchorPoint1;         /* Anchor Point 1 for composites (if not offsets)   */uint16      usAnchorPoint2;         /* Anchor Point 2 for composites (if not offsets)   */transMatrix mulT;                   /* Transformation matrix for composite              */boolean     bUseChildMetrics;       /* Should use child metrics?                        */boolean     bUseMyMetrics;          /* Is glyph USE_MY_METRICS?                         */point       ptDevLSB;               /* Left Side Bearing Point                          */point       ptDevRSB;               /* Right Side Bearing Point                         */uint16      usScanType;             /* ScanType value for this glyph                    */uint16      usSizeOfInstructions;   /* Size (in bytes) of glyph instructions            */uint8 *     pbyInstructions;        /* Pointer to glyph instructions                    */fnt_ElementType * pGlyphElement;    /* Current glyph element pointer                    */};

1624504568_60d3f8f81de17f304ff12.png!small?1624504554775

在fsg_CreateGlyphData中,fsg_AllocateGlyphDataMemory负责计算GlyphData的起始地址,fsg_InitializeGlyphData函数将fsg_WorkSpaceAddr成员pGlyphElement的地址赋给了GlyphData的pGlyphElement。由于结构体fsg_WorkSpaceAddr的内容会覆盖到GlyphData,所以在后续函数fsg_ExecuteGlyph中GlyphData.pGlyphElement上的写入操作就会破坏GlyphData。

事实也是如此,当运行到函数fsg_ExecuteGlyph时,rsi+8(GlyphData+8)落在了memset_0准备设置的内存区间(pGlyphElement数组)内,从而触发越界写。

memset_0调用之后,rsi+8地址处的内容会被清零。

1624504578_60d3f9026b7e7053cf389.png!small?1624504565133

接着是__guard_dispatch_icall_fptr函数的调用,这是微软控制流保护机制,实际调用TrueTypeRasterizer::Implementation::ApplyOutlineVariation函数。ApplyOutlineVariation 会调用GlyphOutlineVariationInterpolator::ApplyVariation,作用是读取ttf文件中的数据,赋值给被memset_0清零的内存区间,rsi+8因为处在区间内,所以值会被改为ttf文件中的内容。

1624504587_60d3f90b9ac0f29accc42.png!small?1624504574315

rsi+8被控制后,可以达到任意位置写,而被写入的内容同样来自于ttf文件,同样可以被控制。这样,就得到了一个任意地址任意写原语。

动态调试

通过windbg动态调试发现(在调用memset_0前打断点),地址rsi+8处的值在调用memset_0后被清零。

1624504607_60d3f91f74d93f417339f.png!small?1624504594205

查看传递给 memset_0 的参数值:

1624504618_60d3f92a90091b4d66959.png!small?1624504605209

计算得出,memset_0准备设置的内存区间为[0x000001f5cdb98bec,0x000001f5cdb98d34],而rsi+8的值为0x000001f5cdb98d18正好落在了上述区间。所以memset_0执行过后,rsi+8地址处的值被置为0。

继续执行,地址rsi+8处的值在调用DWrite!_guard_dispatch_icall_fptr函数前后也发生了变化,并且被修改为poc中的值。

1624504625_60d3f931731b93d5ec0c4.png!small?1624504612226

自此,"add [r8+56h], ax"操作中,r8与ax的值均来自于poc.ttf文件,可以被完全控制,攻击者就可以做到任意地址任意写。

1624504634_60d3f93a9caed6a5ebb19.png!small?1624504621298

补丁

1624504653_60d3f94d1e0cb2f9050f4.png!small?1624504639567

查看补丁文件,发现fsg_WorkSpaceSetOffsets函数作了修改,查阅fsg_WorkSpaceSetOffsets补丁后的伪代码:

1624504659_60d3f95321f9bbd77a6f2.png!small?1624504645608

在原来的逻辑上,又取出了字段maxComponentElements的值(v12 = *((_WORD *)a1 + 14);),并与之比较大小,将数值大者传入函数fsg_GetOutlineSizeAndOffsets。伪代码稍微有点问题,经过汇编代码比较后,v17其实应该是v33。

调试补丁,memset_0准备设置的内存区间为[0x0000017ce0730f64,0x0000017ce07310ac],区间长度仍然为0x148,而rsi+8的值为0x0000017ce0731698落在了区间之外,不再触发数组越界写漏洞。

1624504670_60d3f95eb3f0548560e0b.png!small?1624504657441

总结

DWrite 库文件由于对畸形 ttf 文件的处理不当,导致数组越界写漏洞。攻击者精心构造 ttf 文件后,达到了任意地址任意写,从而能够进一步完成远程代码执行的攻击效果。补丁文件更正了对畸形文件数据的处理逻辑, 使得分配的内存长度更大,避免了越界写的出现。

参考资料

https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2021-24093

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

http://blog.topsec.com.cn/cve-2021-24093-windows图形组件远程执行代码漏洞分析/

https://packetstormsecurity.com/files/161582/Microsoft-DirectWrite-fsg_ExecuteGlyph-Buffer-Overflow.html

评论

暂无
发表评论
 返回顶部 
热度(274)
 关注微信