首页
社区
课程
招聘
[原创]#30天写作挑战#CVE-2020-0729:Windows LNK远程代码执行漏洞分析(二)
发表于: 2020-9-27 20:40 21168

[原创]#30天写作挑战#CVE-2020-0729:Windows LNK远程代码执行漏洞分析(二)

2020-9-27 20:40
21168

CVE-2020-0729:Windows LNK远程代码执行漏洞分析(一)
(接前文,活动结束了,终于有时间把这个漏洞分析写完了。友情提示,在调试过程中,尽量只开一个poc的文件夹,不然可能要递归调用漏洞函数很多次。次数一多,就容易乱了。)

造成该漏洞的主要原因是在处理Leaf Condition中的type为VT_VARIANTPropertyVariant结构时发生错误导致。

如果一个LNK文件有一个ItemID List,且其第一个ItemIDDelegate Folder ItemID,且该Delegate Folder ItemIDd的GUID表示CLSID_SearchFolder,那么首先调用Windows.Storage.Search.dll中的CDBFolder::BindToObject()函数进行处理,该函数会调用CDBFolder::GetFilterConditionForChild()函数从child ItemID中获取search filter condition。

第一阶段,CDBFolder::GetFilterConditionForChild()函数在进行查找时,会在child ItemID中搜索PKEY_FilterInfo property,如果找到了,在序列化property store中的包含一个property bag的VT_STREAM就会通过调用SHLoadFilterFromStream()函数进行加载,该函数会创建一个CFilterCondition对象然后提供给IUnknown_LoadFromStream()函数。该操作会调用CFIlterCondition::Load()函数,而该函数会首先将property bag复制到一个内存中的store中,然后开始校验该store的结构,通过查找名称为Name, Type,key:FMTID,Key:PID以及Condition的字符串来确认是否property bag元素。

第二阶段,在确认完所有元素后,调用LoadConditionFromStream()函数来读取Condition property bag中的VT_STREAM,该元素调用IUnknown_LoadKnownImplFromStream()来读取Condition GUID并从StructuredQuery.dll中创建并加载相关的condition对象。在从stream中加载condition对象的过程中,所有嵌套的对象会按照出现的顺序进行加载,包括AttributesConditions。当遇到一个Leaf Condition时,会调用StructuredQuery1::LeafCondition::Load()函数来读取所有的Attributes,然后读取Condition Type PKEYCondition Operation,然后调用StructuredQuery1::ReadPROPVARIANT()函数来读取PropertyVariant结构。

第三节阶段,StructuredQuery1::ReadPROPVARIANT()先读取2字节的type,然后检查VT_ARRAY(0x2000)是否进行了设置,这里主要是因为StructuredQuery不支持。然后进入到一个switch语句来根据type进行不同的处理。如果type设置为VT_VARIANT(0x000c),就会检查完整的type字段来确认是否设置了VT_VECTOR,如果没有设置,则调用CoTaskMemAlloc()来分配包含另一个PropertyVariant结构的24字节的缓冲区。在递归调用StructuredQuery1::ReadPROPVARIANT()函数以读取紧随VT_VARIANT 的type字段之后的另一个PropertyVariant结构之前,并没有对这个缓冲区进行初始化。如果下一个type字段设置为VT_CF(0x0047),则应该是一个包含一个指向剪贴板数据的指针的PropertyVariant结构,ReadPROPVARIANT()函数尝试将stream的后4个字节写入先前分配的24字节缓冲区中的8字节值所指向的位置。由于缓冲区并没有进行初始化,数据会被写入到一个未定义的位置,最终导致任意代码执行。

用一个简单的流程图来描述上面的流程:


首先是StructuredQuery1::LeafCondition::Load()函数。在StructuredQuery1::LeafCondition::Load()函数中依次读取了Condition Type PKEYCondition Operation,然后调用StructuredQuery1::ReadPROPVARIANT()函数读取PorpertyVariant结构:

进入到StructuredQuery1::ReadPROPVARIANT()函数内部:

这里首先读取variant的类型,然后进行VT_ARRAY检查,再然后检测type是否为0x13,而0x13为VT_UI4的code。如果不是,进入到后续的switch结构的处理流程:


VT_CF的code为71,该类型表示指向CLIPDATA结构的一个指针,Propvaritant type为pclipdataCLIPDATA的详细结构如下(与一般的PROPVARIANT略有不同):

VT_CF时的处理代码如下:

如果没有设置VT_VECTOR,则跳转到如下代码。因为在处理VT_CF时,对分配的缓冲区并没有做任何的初始化处理,然后直接将buffer的地址传入了rdx,并且向rdx的地址中写入了4个字节:

VT_VARIANT为一个DWORD类型的标识后续值的类型的标识符,仅可以与VT_VECTORVT_BYREF结合使用。VT_VARIANT时的处理代码如下:


跳转到loc_18003bcb0:

检查是否设置了VT_VECTOR,如果没有设置,则调用CoTaskMemAlloc()来分配包含另一个PropertyVariant结构的24字节的buffer,且后续在未进行buffer初始化的情况下,函数进行递归调用。

至此,漏洞的静态分析过程大致完成。对于中间过程中各参数和返回值的具体表现情况,需要通过动态调试来观察。

首先直接观察下最终的crash现场,看下栈回溯:

并没有全部截图,但关键部分的相关调用已足够。

ISream_Read()函数此时的3个参数分别为:0, 00000000`0e5ca200,2,该函数的原型为:

而调用该函数的位置恰好在写入4字节数据的那里(loc_18003bb98),那么此时读取出来的数据是已经被篡改过的数据了。继续向上追溯,StructuredQuery!StructuredQuery1::ReadPROPVARIANT+0x31a39处为函数递归调用的位置:

目前根据栈回溯结果,大致与前面静态分析的过程一致,下面进入漏洞触发过程的详细分析:

首先在StructuredQuery!StructuredQuery1::LeafCondition::Load+0xad处下断,观察`StructuredQuery!StructuredQuery1::ReadPROPVARIANT()函数调用时的状态:

继续执行,来到variant类型校验:


继续执行,判断是否为VT_UI4

此时eax的值为0x1f,不等于0x13,进入到后续的switch处理流程:

再往后执行,也没有触发漏洞代码,来到第二次调用:

而第二次及第三次调用与第一次调用流程一致(仅仅流程一致,部分关键地址和数据有变化),来到第四次调用:

第四次调用在比较是否为0x13时,判断成立,进入与前几次调用不同的流程:

在检查完VT_VARIANTVT_VECTOR的相关设置后,分配24字节的buffer:

这里看下CoTaskMeMAlloc()函数的函数原型:

函数很简单,分配cb字节大小的内存,此处的cb = 0x18 = 24,函数成功分配后返回内存块地址:

返回后,对该buffer没有做任何处理,直接将buffer地址赋值给rdx做为递归调用的参数进行使用:

带着未初始化的buffer,进入到递归调用过程:

继续向下,读取variant的类型:

此次读取出的类型为VT_CF(0x47)类型。在进行是否为0x13的比较时,[r14]中保存着buffer的地址,eax中保存着类型:

继续向下执行,来到这里后首先看下buffer中现在的情况:

然后继续向下:

继续:

继续:

然后,从stream中写4字节数据到加载的地址(rcx)。此时IStream_Read()函数的参数情况如下:

很明显,此时的pv是一个错误值,从而导致stream中的4个字节的数据写入到0000002800000037这个错误地址。进入IStream_Read()函数:

后续使用的非法地址略有变动,变为rdx=006f004600740072(别问为什么,问就是跑飞了重来的!!!)

进入ntdll!LdrpDispatchUserCallTarget()函数之前:

然后来到crash的函数:combase!CMemStm::Read()函数处:

继续向下:

这里r8为字节数,rdx中存放的是buffer+8地址处的内容(未定义地址),rcx中存放的stream的地址。继续向下,看后续如何对rdx中的未定义地址进行处理:

调用memcpy()函数进行data的复制,dst = rcx = 006f004600740072,src = rdx = 006f004600740072,size = r8 = 4。正如前面分析所得,目的地址是一个未定义地址:

跟进memecpy()函数可以清楚地看到最终的问题所在:

至此为止,漏洞的完整Root Case已分析完成。

基本条件

触发过程

attacker发送一个LNK文件给target:

应用协议:

已知文件类型:Shell Link

已知扩展名:LNK

已知MIME类型:application/x-ms-shortcut

从常规漏洞防御角度来说,文件格式类漏洞更适合使用终端防护软件进行防护。但该漏洞的主要应用场景应该是attacker诱骗target打开LNK文件,这样就会涉及到流量传输,在一定程度上可以对恶意流量进行检测:

attacker诱骗target请求恶意LNK文件:

attacker直接响应一个恶意LNK(注意分包):


StructuredQuery1::ReadPROPVARIANT()函数的补丁对比结果:

总体上看,函数并没有做过多修改,进入查看具体更新内容。当case为12(VT_VARIANT类型)时,有一处比较明显的改动。在进行24字节的缓冲区分配后,对分配的缓冲区做了赋值0操作:

在更新前,对分配的buffer未做任何的初始化操作,直接进入了递归调用:

在更新后,对分配完的buffer做了初始化操作(3次赋值为0,总计24字节):

所以补丁的思路很简单,将分配的24字节的buffer初始化为0即可。

通过动态调试,证明了这种补丁思路:

进行了3次mov操作后,将24个字节的buffer成功初始化为0:


使用初始化为0后的buffer进入函数递归:

继续调用IStream_Read()函数进行读取,此时的参数情况如下:

继续向下跟,会发现并没有在IStream_Read()函数中产生补丁前的那种情况,而是执行完后,跳到异常处理,函数返回,而不进行内存分配:

针对该漏洞,目前很难做到无损检测。

流量防御:流量侧的防御可以检测恶意数据的具体内容,但需要联系数据上下文,因为该漏洞的本质是多种数据结构的组合使用导致异常。流量特征较弱,不是很建议使用流量防御。

终端防御:使用热补丁,对24个字节的buffer使用0进行初始化即可。

终端防御方法基本没有风险,但是流量侧误报风险较大。

看雪论坛内部共享,未经允许请勿擅自转载。谢谢。

 
 
 
 
 
 
 
 
 
 
 
 
typedef struct  tagCLIPDATA {
    // cbSize is the size of the buffer pointed to
    // by pClipData, plus sizeof(ulClipFmt)
    ULONG              cbSize;
    long               ulClipFmt;
    BYTE*              pClipData;
    } CLIPDATA;
typedef struct  tagCLIPDATA {
    // cbSize is the size of the buffer pointed to
    // by pClipData, plus sizeof(ulClipFmt)
    ULONG              cbSize;
    long               ulClipFmt;
    BYTE*              pClipData;
    } CLIPDATA;

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-9-28 17:32 被有毒编辑 ,原因: 补充内容
收藏
免费 5
支持
分享
最新回复 (6)
雪    币: 4904
活跃值: (1440)
能力值: ( LV9,RANK:246 )
在线值:
发帖
回帖
粉丝
2
mark
2020-9-28 10:23
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
厉害,好多高手
2020-10-3 18:05
0
雪    币: 5574
活跃值: (3597)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
可以发下poc的链接吗
2020-12-5 17:22
0
雪    币: 114
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
您好,POC链接方便发下吗?
2021-7-16 11:37
0
雪    币: 15187
活跃值: (16852)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
6
路远_人尚在 您好,POC链接方便发下吗?
poc目前互联网没有公开,属于公司内部资料,我没法公开,抱歉。。
2021-7-19 08:46
0
雪    币: 114
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
理解
2021-7-22 16:37
0
游客
登录 | 注册 方可回帖
返回
//