聚焦源代码安全,网罗国内外最新资讯!
编译:奇安信代码安全卫士
作者:BORIS LARIN、COSTIN RAIU 和 BRIAN BARTHOLOMEW
本文作者在分析 CVE-2021-1732 的 exploit 时,发现了和 BITTER APT 组织相关的另外一个 0day exploit。2月份作者将新 exploit 告知微软,后者证实确实为 0day 并分配编号 CVE-2021-28310。微软在4月补丁星期二中修复了该漏洞。如下是对作者博客文章的节选编译。
我们认为该 exploit 已遭在野利用,可能已被多个威胁行动者所利用。它是一个提权exploit,很可能会被和其它浏览器 exploit 结合使用逃逸沙箱或获得系统权限以进一步访问系统。遗憾的是,我们无法捕捉到完整的利用链,因此目前不清楚该漏洞是否已被攻击者结合其它浏览器0day或已知已修复漏洞利用。
技术详情
CVE-2021-28310 是位于 dwmcore.dll 中的一个界外写漏洞,dwmcore.dll 是 Desktop Window Manager (dwm.exe) 的一部分。由于缺乏边界检查,攻击者可创建一种情境,使用 DirectComposition API 在受控的偏移处写受控数据。DirectComposition 是一个 Windows 组件,自 Windows 8 起引入,用于启用具有变换、效果和动画的位图合成,并支持不同来源的位图(GDI、DirectX 等)。之前我们曾发布文章分析了关于滥用 DirectComposition API 的在野 0day。DirectComposition API 由 win32kbase.sys 驱动程序实现,所有相关的系统调用名称均以字符串 “NtComposition” 开头。
实施利用仅需三个系统调用:NtDCompositionCreateChannel、 NtDCompositionProcessChannelBatchBuffer 和NtDCompositionCommitChannel。NtDCompositionCommitChannel 系统调用初始化可与 NtDCompositionProcessChannelBatchBuffer 组合利用的信道,以批量模式一次发送多个 DirectComposition 命令,供内核处理。为此,需要在由 NtDCompositionCommitChannel 映射的特殊缓冲区中连续写命令。每个命令都有自己的格式,变量长度和参数清单。
enum DCOMPOSITION_COMMAND_ID
{
ProcessCommandBufferIterator,
CreateResource,
OpenSharedResource,
ReleaseResource,
GetAnimationTime,
CapturePointer,
OpenSharedResourceHandle,
SetResourceCallbackId,
SetResourceIntegerProperty,
SetResourceFloatProperty,
SetResourceHandleProperty,
SetResourceHandleArrayProperty,
SetResourceBufferProperty,
SetResourceReferenceProperty,
SetResourceReferenceArrayProperty,
SetResourceAnimationProperty,
SetResourceDeletedNotificationTag,
AddVisualChild,
RedirectMouseToHwnd,
SetVisualInputSink,
RemoveVisualChild
};
虽然这些命令由内核处理,但同时也被序列化到另外一种格式并由 Local Procedure Call (LPC) 协议传递到 Desktop Window Manager (dwm.exe) 进程以渲染到屏幕。这个程序可由第三个系统调用 NtDCompositionCommitChannel 进行初始化。
为触发该漏洞,所发现的 exploit 使用了三种命令:CreateResource、ReleaseResource 和 SetResourceBufferProperty。
void CreateResourceCmd(int resourceId)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = CreateResource;
buf[1] = resourceId;
buf[2] = PropertySet; // MIL_RESOURCE_TYPE
buf[3] = FALSE;
BatchLength += 16;
}
void ReleaseResourceCmd(int resourceId)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = ReleaseResource;
buf[1] = resourceId;
BatchLength += 8;
}
void SetPropertyCmd(int resourceId, bool update, int propertyId, int storageOffset, int hidword, int lodword)
{
DWORD *buf = (DWORD *)((PUCHAR)pMappedAddress + BatchLength);
*buf = SetResourceBufferProperty;
buf[1] = resourceId;
buf[2] = update;
buf[3] = 20;
buf[4] = propertyId;
buf[5] = storageOffset;
buf[6] = _D2DVector2; // DCOMPOSITION_EXPRESSION_TYPE
buf[7] = hidword;
buf[8] = lodword;
BatchLength += 36;
}
我们先来看下 dwmcore.dll 中的CPropertySet::ProcessSetPropertyValue 函数。该函数负责处理 SetResourceBufferProperty 命令。我们对负责处理 DCOMPOSITION_EXPRESSION_TYPE = D2DVector2 的代码最感兴趣。
int CPropertySet::ProcessSetPropertyValue(CPropertySet *this, ...)
{
...
if (expression_type == _D2DVector2)
{
if (!update)
{
CPropertySet::AddProperty<D2DVector2>(this, propertyId, storageOffset, _D2DVector2, value);
}
else
{
if ( storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF )
{
goto fail;
}
CPropertySet::UpdateProperty<D2DVector2>(this, propertyId, _D2DVector2, value);
}
}
...
}
int CPropertySet::AddProperty<D2DVector2>(CResource *this, unsigned int propertyId, int storageOffset, int type, _QWORD *value)
{
int propertyIdAdded;
int result = PropertySetStorage<DynArrayNoZero,PropertySetUserModeAllocator>::AddProperty<D2DVector2>(
this->propertiesData,
type,
value,
&propertyIdAdded);
if ( result < 0 )
{
return result;
}
if ( propertyId != propertyIdAdded || storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF )
{
return 0x88980403;
}
result = CPropertySet::PropertyUpdated<D2DMatrix>(this, propertyId);
if ( result < 0 )
{
return result;
}
return 0;
}
int CPropertySet::UpdateProperty<D2DVector2>(CResource *this, unsigned int propertyId, int type, _QWORD *value)
{
if ( this->properties[propertyId]->type == type )
{
*(_QWORD *)(this->propertiesData + (this->properties[propertyId]->offset & 0x1FFFFFFF)) = *value;
int result = CPropertySet::PropertyUpdated<D2DMatrix>(this, propertyId);
if ( result < 0 )
{
return result;
}
return 0;
}
else
{
return 0x80070057;
}
}
对于表达式类型被设置为 D2DVector2 的 SetResourceBufferProperty 命令而言,函数 CPropertySet::ProcessSetPropertyValue(…) 要么调用 CPropertySet::AddProperty<D2DVector2>(…),要么调用 CPropertySet::UpdateProperty<D2DVector2>(…),具体取决于更新flag 是否在命令中设置。首先抓住眼球的是新属性被添加到函数CPropertySet::AddProperty<D2DVector2>(…) 中的方式。可以看到,它在资源中新增了新属性,但仅检查增加新属性后,新属性的 propertyId 和 StorageOffset 是否和所提供的值相等,如果不相等则返回错误消息。在工作结束后才进行检查是一种不良的编程实践,可导致漏洞的产生。然而,函数 CPropertySet::UpdateProperty<D2DVector2>(…) 中发生了一个真正的问题。检查不会确保所提供的 propertyId 是否小于添加到资源中的属性的个数。因此,如果攻击者设法绕过了属性数组中对数据的两个额外检查,则攻击者可使用该函数通过 propertiesData 缓冲区执行 OOB 写操作。
(1) storageOffset == this->properties[propertyId]->offset & 0x1FFFFFFF
(2) this->properties[propertyId]->type == type
如攻击者能够在 dwm.exe 进程中分配并发布对象,将堆收集为想要的状态并通过虚假属性在具体位置喷射内存,则可绕过这些检查。所发现的 exploit 使用命令 CreateResource、ReleaseResource 和 SetResourceBufferProperty 做到了这一点。
在本文成稿时,我们仍未分析到修复该漏洞的更新二进制,但为了排除该漏洞的其它变体,微软也需要检查其它表达式类型属性的数量。
即使 dwmcore.dll 中出现了上述问题,但如果实现了所想要的内存状态,绕过之前提到的检查,并发布批量命令触发该漏洞,但还鉴于另外一个因素的存在,仍无法触发该漏洞。
如上所述,这些命令首先由内核处理,之后被发送到 Desktop Windows Manager (dwm.exe)。这意味着如果你尝试发送具有不合法 propertyId 的命令,则 NtDCompositionProcessChannelBatchBuffer 系统调用将返回出错消息,而该命令也不会被传递到 dwm.exe 进程。SetResourceBufferProperty 命令的表达式类型被设置为 D2DVector2,在 win32kbase.sys 驱动程序中和函数 DirectComposition::CPropertySetMarshaler::AddProperty<D2DVector2>(…) 以及 DirectComposition::CPropertySetMarshaler::UpdateProperty<D2DVector2>(…) 一起处理,这和 dwmcore.dll 中的函数非常类似(可能是被复制粘贴的)。然而,函数 UpdateProperty<D2DVector2> 的内核版本还存在一个显著差异:它实际上会检查被添加到资源中的属性的数量。
int DirectComposition::CPropertySetMarshaler::UpdateProperty<D2DVector2>(DirectComposition::CPropertySetMarshaler *this, unsigned int *commandParams, _QWORD *value)
{
unsigned int propertyId = commandParams[0];
unsigned int storageOffset = commandParams[1];
unsigned int type = commandParams[2];
if ( propertyId >= this->propertiesCount
|| storageOffset != this->properties[propertyId]->offset & 0x1FFFFFFF)
|| type != this->properties[propertyId]->type )
{
return 0xC000000D;
}
else
{
*(_QWORD *)(this->propertiesData + (this->properties[propertyId]->offset & 0x1FFFFFFF)) = *value;
...
}
return 0;
}
检查 UpdateProperty<D2DVector2> 函数内核模式版本中的 propertiesCount 进一步阻止了用户模式 twin 对恶意命令的处理并缓解了该漏洞,但这正是 DirectComposition::CPropertySetMarshaler::AddProperty<D2DVector2>(…) 发挥作用的地方。AddProperty<D2DVector2> 函数的内核版本运作方式完全和其用户模式变体一致且也应用了同样的行为:在添加后检查属性,且如果所创建属性的 propertyId 和 storageOffset 不匹配所提供的价值后返回错误。鉴于此,有可能使用函数 AddProperty<D2DVector2> 增加一个新属性并强制该函数返回错误并引发分配到内核模式/用户模式的属性数量之间不一致。在内核中的 propertiesCount 检查可通过这种方法被绕过,而恶意命令将被传递到 Desktop Windows Manager (dwm.exe)。
被分配到内核/用户模式中相同资源的属性数量之间的不一致可能造成其它漏洞,因此我们建议微软更改函数 AddProperty 的行为并在添加前首先检查属性。
利用流程总结
所发现exploit 的整个利用流程如下:
1、创建大量资源,特定大小的属性使堆变为可预测状态。
2、通过特定大小和内容的属性创建额外资料,通过虚假属性在特定位置喷射内存。
3、发布在第2阶段创建的资源。
4、创建具有属性的其它资源。这些资源将用于执行 OOB 写操作。
5、在第1阶段创建的资源中制造漏洞。
6、为第4阶段创建的资源创建额外属性。它们的缓冲区会被分配到特定位置。
7、创建“特别”属性,引起在第4阶段所创建资源被分配到内核模式/用户模式中的同样资源的属性数量不一致。
8、使用 OOB 写漏洞写 shellcode,创建对象并执行代码。
9、在另外一个系统进程中注入其它 shellcode。
推荐阅读
Windows “七大奇迹”:DNS Dynamic Updates 中的7个严重漏洞
谷歌发布 Windows 10 图形组件 RCE 漏洞的详情
详述 Discord Desktop app RCE 挖洞经过,最后得$5000 + $300 (含 PoC 视频)
原文链接
https://securelist.com/zero-day-vulnerability-in-desktop-window-manager-cve-2021-28310-used-in-the-wild/101898/
题图:Pixabay License
本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。
奇安信代码卫士 (codesafe)
国内首个专注于软件开发安全的
产品线。
觉得不错,就点个 “在看” 或 "赞” 吧~