备注说明:该漏洞分析内容实在有点多,打算分开发,本次没有附带动态调试。后续会把详细的动态调试部分拿上来,大家先消化下背景知识和静态流程。
Windows与其他OS一样,支持link的使用,link主要用于存储一个位置(或路径)的调用。在UNIX系统中,称为符号链接。在Windows中,这些二进制对象被定义为"shell link",文件名后缀为".LNK"。这是Shell Link Binary File Format的规范,是一种数据对象,其中包含可用于访问另一个数据对象的信息。
Shell link通常用于支持应用程序启动和链接方案,例如对象链接和嵌入(OLE),但也可以由需要存储对目标文件的引用的功能的应用程序使用。
该漏洞主要是由于对LNK文件的错误解析造成,远程攻击者可以诱惑目标用户浏览包含特制LNK文件的文件夹或者下载一个LNK文件。成功的利用可以实现在当前用户的安全上下文中进行任意代码执行。
• Microsoft Windows 7 SP1<br> • Microsoft Windows 8.1<br> • Microsoft Windows 10(1607-1909)<br> • Microsoft Windows RT 8.1<br> • Microsoft Windows Server 2008 SP2<br> • Microsoft Windows Server 2008 R2 SP1<br> • Microsoft Windows Server 2012<br> • Microsoft Windows Server 2012 R2<br> • Microsoft Windows Server 2016<br> • Microsoft Windows Server 2019<br> • Microsoft Windows Server version 1803 (Server Core Installation)<br> • Microsoft Windows Server version 1903 (Server Core Installation)<br>• Microsoft Windows Server version 1909 (Server Core Installation)<br>
微软官方针对该漏洞已发布安全更新补丁,补丁地址:
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0729
靶机:Windows 10 1803 x64
靶机操作:在任意位置创建文件夹,将poc文件放入文件夹中
直接双击打开包含poc文件的文件夹(无需双击poc文件,只需双击包含poc的文件夹即可),explorer.exe崩溃重启。
(限于篇幅问题,此处不对.LNK的完全结构做详细记录,而只是介绍一个合法的最小化的结构,感兴趣的同学可以到参考文献中的链接1进行详细的结构学习。结构介绍中所有图片均以BaiduNetdisk.exe
的link文件为样例。)
LNK格式可以表达如下:
SHELL_LINK_HEADER
:一个ShellLinkHeader
结构,包含了确认信息,时间戳,以及指明一些可选结构是否存在的flags。
LINKTARGET_IDLIST
:一个可选的LinkTargetIDList
结构,指定了link的target。
LINKINFO
:一个可选的LinkInfo
结构,指明了处理link target必需的信息。
STRING_DATA
:0个或多个StringData
结构,用于传递用户接口和路径标识信息。
EXTRA_DATA
:0个或多个和ExtraData
结构。
备注:
ShellLinkHeader
是每个shell link文件必须包含的结构,其结构如下:
以上所有结构除特殊说明外,均为小端序。
LinkFlags
字段指定了是否存在可选结构以及各种选项,例如shell link中的字符串是否以Unicode编码,其结构如下:
在大多数情况下设置的一个标志HasLinkTargetIDList
由位置“ A”表示,即LinkFlags
字段的第一个字节的最低有效位,如果已设置,则LinkTargetIDList
结构必须紧跟Shell Link Header
。一个例子的该字段的详细结构如下:
LinkTargetIDList
结构指定了link的target,其结构如下:
如上图所示,首先是IDListSize
结构,然后跟了6个ItemID
结构(详细结构稍后介绍)的IDList
。IDList
数组指定了一个持久化的item ID list的结构:
ItemIDList
的作用实际上与文件路径的作用相同,其中的每个ItemID
结构对应于类似路径的层次结构中的一个路径组件。ItemID
可以引用实际的文件系统文件夹,例如“控制面板”或“保存的搜索”之类的虚拟文件夹,或充当执行特定功能的“快捷方式”的其他形式的嵌入式数据。
通常情况下,ItemID
的结构如下所示:
从偏移量0x0004开始的两个字节的值与ItemSize
和ItemType
结合使用来帮助确定ItemID
的类型。例如,如果ItemSize
的值为0x14,ItemType
的值为0x1f,在0x0004的2个字节的值如果大于ItemSize
,那么就认为ItemID
的剩余部分由一个16字节的GUID组成,这通常是LNK文件中指向标准文件的第一个ItemID
的结构;如果ItemSize
的值大于包含GUID所需的值,但是小于0x0004偏移处的值,那么在GUID的后面会跟 一个ExtraDataBlock
,block开始为一个2字节的size,后续包含Version
和Signature
。
更多关于ItemIDList
和ItemID
的详细信息可以参见参考文献中的ItemID
部分。下面的示意图可以更好地理解以上的完整结构:
从图中可以明显看出各种结构的具体的值,与前面的结构相符合。比如Header中的GUID值为{00021401-0000-0000-C000-000000000046}
,LinkFlags
字段的HasLinkTargetIDList
设置为1,所以在Header后面紧跟了多个LinkTargetIDList
结构,每个结构指向一个target,第一个结构的Value为MyComputer
,往后依次为D:\
,Program File(x86)
等等,最后一个为BaiduNetdisk.exe
,而所有的LinkTargetIDList
组合起来,为该LNK文件指向的实际的application的绝对路径。而第一个LinkTargetIDList
时一个虚拟的文件系统文件夹,后面的均为实际的文件系统文件夹。在所有LinkTargetIDList
结构的末尾是一个2字节大小的TerminalID
,其值为0。
Windows允许用户以2种方式来保存或创建搜索查询的快捷方式。第一种方式是将查询保存为一个XML文件,扩展为".search-ms",该种格式只有一部分内容以文档形式进行了公开。第二种方式是将".search-ms"文件进行序列化然后直接嵌入到LNK文件中,最终的文件包含一个IDList
结构,该结构以一个Delegate Folder ItemID
开始,后面跟一个User Property View ItemID
用于指定搜索查询。
第一种方式:
而打开保存的文件内容可以看到都是xml元素:
第二种方式:
点击前面的搜索图标,会显示保存为一个.search-ms文件,然后按住红框中的图标,直接拖拽到一个路径下,会创建一个LNK文件。而该LNK文件的作用与第一种方式的作用相同,都保存了一样的搜索操作。
查看文件内容,是一个标准的LNK文件:
Delegate Folder ItemID
的0x0004偏移处的2个字节是一个表示其余结构的大小的字段,总体结构如下:
LNK文件中的所有GUID均以RPC IDL形式进行存储,即GUID的前3个段均以小端序存储(例如DWORD,WORD等),后2个段均以独立字节存储。例如,GUID{01234567-1234-ABCD-9876-0123456789AB}的二进制表示为:\x67\x45\x23\x01\x34\x12\xCD\xAB\x98\x76\x01\x23\x45\x67\x89\xAB
。
Delegate Folder ItemID
中的详细函数并没有进行公开,但其作用大概是在GUID字段中指定一个class,对后续的ItemID
都使用GUID指定的class来进行处理,以这种方式以该class为层次结构的"root"。对于包含嵌入式数据的LNK文件来说,Item GUID
为{04731B67-D933-450A-90E6-4ACD2E9408FE}
,对应于CLSID_SearchFolder
,即对Windows.Storage.Search.dll
的引用。
Delegate Folder ItemID
后面跟一个User Property View ItemID
,其结构如下:
在以上字段中,与漏洞关系比较大的是PropertyStoreList
字段。该字段如果存在,那么会包含一个或多个序列化的PropertyStore
iterm,其结构如下所示:
Property Store Data
字段是序列化property store中的一系列properties,在PropertyStore
中的所有的properties均属于Property Format GUID
标识的class。每个property通过一个数字ID进行标识,当与Property Format GUID
结合使用时,该ID称为property key或者PKEY
。
如果Property Format GUID
等于{D5CDD505-2E9C-101B-9397-08002B2CF9AE}
,那么PKEY
的确定方式略有不同。每个property当作Property Bag
的一部分,格式如下:
Property bags
通常包含类似名称为"Key:FMTID"或"Key:PID"的内容,这些名称表示了解释其他元素的特定的PKEY
。
如果Property Format GUID
不是前面提到的Property Bags
形式的GUID,那么每个property将会使用一个整数形式的PID
进行标识,其结构如下:
TypePropertyvalue
对应于property集中的property的类型值,property集遵循 Microsoft Object Linking and Embedding (OLE) Property Set Data Structures 中的规范(详见参考文献3)。
各种PKEY
由Windows SDK中的头文件进行定义,但是,许多文件没有记录,只能通过检查相关库的调试符号中的引用来识别。对于包含嵌入式搜索数据的LNK文件来说,User Property View ItemID
中的第一个PropertyStore
有一个Property Format GUID
,其值为{1E3EE840-BC2B-476C-8237-2ACD1A839B22}
,其结构中的Id
值为2,对应于PKEY_FilterInfo
。
PKEY_FilterInfo
的TypedPropertyValue
字段包含一个VT_STREAM
属性。通常情况下,一个VT_STREAM
property包含的type值为0x0042,2字节的padding,和一个IndirectPropertyName
。IndirectPropertyName
指明了一个备用流,该备用流或者包含simple property set存储中的PropertySetStream
数据包,或者包含non-simple property set存储中的CONTENTS
流数据(详细内容参考文献4)。该名称由宽字符的字符串"prop"开始,其后跟对应于PropertySet
数据包中的property标识符的十进制字符串。
然而,因为LNK文件使用嵌入在VT_SREAM
中的序列化property store,IndirectPropertyName
仅仅检查是否以"prop"开头,而value本身会被忽略,这就导致TypedPropertyValue
的结构变成如下形式:
Stream Data
中的数据取决于stream property属于的特定的PKEY
。对于PKEY_FilterInfo
来说,Stream Data
通常包含一个嵌入的PropertyStoreList
,其中包含了多个序列化的PropertyStore
结构。此时,Stream Data
的结构如下所示:
PKEY_FilterInfo
stream 中的嵌套PropertyStoreList
实际上是.search-ms
文件中"conditions"标记的序列化版本。下面是一个conditions tag的样例结构:
"attribute"标签的详细功能尚未公开,但是该标记包含一个对应于CONDITION_HISTORY
的GUID
,一个对应于StructuredQuery
的CConditionHistory
类的CLSID
,这表示嵌套的condition和attribute结构代表着之前已保存的搜索查询记录。通常情况下,attribute
标签的chs
可以确定可选history是否存在(猜测)。当此结构序列化到property store中时,会将其放入PKEY_FilterInfo PropertyStoreList
中,它采用带有上述Property Format GUID
的property bag的形式。特别情况下,序列化的Conditions
结构包含在一个使用名称为Condition
进行标识的VT_STREAM Property
中。此时,PropertyStore
item的结构如下:
Condition object
通常是一个Leaf Condition
或者Compound Condition
对象,其中可以包含嵌套对象,通常包含一个或多个Leaf Condition
,也可能包含其他的Compound Condition
对象。2种condition object都具有以下结构:
对于Leaf Condition
来说,其Conditioin GUID
为{52F15C89-5A17-48E1-BBCD-46A3F89C7CC2}
;对于Compound Condition
来说,其Conditioin GUID
为{116F8D13-101E-4FA5-84D4-FF8279381935}
。Attributes
字段由几个Attributes
attribute结构组成,每个对应于之前提到的search-ms文件中的attribute标签,字段结构如下:
Attribute
的其他结构取决于AttributeID
和CLSID
。对于前面的attributeID
为{9554087B-CEB6-45AB-99FF-50E8428E860D}
,CLSID
为{C64B9B66-E53D-4C56-B9AE-FEDE4EE95DB1}
的CONDIOTION_HISTORY
attribute来说,其剩余结构为一个ConditionHistory
对象,该对象结构如下:
如果nested_condition
的值大于0,CONDITION_HISTORY
就会有嵌套的condition object,该对象本身也可能具有具有嵌套条件的嵌套属性,依此类推。
在完全读取了所有的嵌套结构后,Compoound Condition
和Leaf Condition
结构后续就不同了。紧接Attributes
字段后面,Compound Condition
的结构如下:
numFixedObjects
字段指明了后续会跟多少附加conditions(通常为Leaf Conditions
)。
而对于Leaf Condition
,其结构如下:
TokenInfomationComplete
结构存在与否取决于前面的flag是否进行了设置,如果没有设置那么紧跟下一个flag;如果进行了设置,那么紧跟的结构如下:
对于Leaf Condition
的PropertyVariant
字段,其结构大致与PROPVARIANT
结构(参考文献5)相似。PropertyVariant
结构包含2字节的type,其后为该type的data。重要的是要注意,由于参考文献5中指定的填充字节往往不存在,因此StructuredQuery
似乎对PROPVARIANT
结构进行了自定义实现。 还需要注意的是,值0x1000或VT_VECTOR
与另一种类型结合使用,意味着将有多个指定类型的值。
简述一个具有保存的搜哦的LNK文件的最简结构如下所示(去除无关结构):
以上只是一个最简单的使用单个Leaf Condition
结构的搜索LNK结构,真实情况种往往是从Compund Condition
开始,别切包含很多嵌套结构(包括许多的Leaf Condition
)。
造成该漏洞的主要原因是在处理Leaf Condition
中的type为VT_VARIANT
的PropertyVariant
结构时发生错误导致。
如果一个LNK文件有一个ItemID List
,且其第一个ItemID
为Delegate Folder ItemID
,且该Delegate Folder ItemID
d的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对象的过程中,所有嵌套的对象会按照出现的顺序进行加载,包括Attributes
和Conditions
。当遇到一个Leaf Condition
时,会调用StructuredQuery1::LeafCondition::Load()
函数来读取所有的Attributes
,然后读取Condition Type PKEY
和Condition 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字节值所指向的位置。由于缓冲区并没有进行初始化,数据会被写入到一个未定义的位置,最终导致任意代码执行。
用一个简单的流程图来描述上面的流程:
(未完待续) 大家稍等哈,最近有活动,实在是忙不开,到时候我看看直接把震网的也一起加进来,抱歉
SHELL_LINK
=
SHELL_LINK_HEADER [LINKTARGET_IDLIST] [LINKINFO]
[STRING_DATA]
*
EXTRA_DATA
SHELL_LINK
=
SHELL_LINK_HEADER [LINKTARGET_IDLIST] [LINKINFO]
[STRING_DATA]
*
EXTRA_DATA
1
2
3
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
0
|
1
|
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|A|
0
|
0
|
0
|
0
|
0
|
|A|
1
2
3
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
0
|
1
|
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|A|
0
|
0
|
0
|
0
|
0
|
|A|
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2020-9-27 20:17
被有毒编辑
,原因: