备注说明:该漏洞分析内容实在有点多,打算分开发,本次没有附带动态调试。后续会把详细的动态调试部分拿上来,大家先消化下背景知识和静态流程。
一、漏洞信息
1. 漏洞简述
- 漏洞名称:Microsoft Windows LNK Remote Code Execution Vulnerability
- 漏洞编号:CVE-2020-0729
- 漏洞类型:Memory Corruption
- CWE-098:Use of Uninitialized Resource
- 漏洞影响:Code Execution
- CVSS评分:7.5
- 利用难度:Medium
- 基础用户:需要
2. 组件概述
Windows与其他OS一样,支持link的使用,link主要用于存储一个位置(或路径)的调用。在UNIX系统中,称为符号链接。在Windows中,这些二进制对象被定义为"shell link",文件名后缀为".LNK"。这是Shell Link Binary File Format的规范,是一种数据对象,其中包含可用于访问另一个数据对象的信息。
Shell link通常用于支持应用程序启动和链接方案,例如对象链接和嵌入(OLE),但也可以由需要存储对目标文件的引用的功能的应用程序使用。
3. 漏洞利用
该漏洞主要是由于对LNK文件的错误解析造成,远程攻击者可以诱惑目标用户浏览包含特制LNK文件的文件夹或者下载一个LNK文件。成功的利用可以实现在当前用户的安全上下文中进行任意代码执行。
4. 漏洞影响
• 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>
5. 解决方案
微软官方针对该漏洞已发布安全更新补丁,补丁地址:
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0729
二、漏洞复现
1. 环境搭建
2. 复现过程
直接双击打开包含poc文件的文件夹(无需双击poc文件,只需双击包含poc的文件夹即可),explorer.exe崩溃重启。
三、漏洞分析
1. 基本信息
- 漏洞文件:
StructuredQuery.dll
- 漏洞函数:
StructuredQuery1::ReadPROPVARIANT()
- 漏洞对象:
VT_VARIANT PropertyVariant
元素创建的一个24字节的数据缓冲区
2. 背景知识
(限于篇幅问题,此处不对.LNK的完全结构做详细记录,而只是介绍一个合法的最小化的结构,感兴趣的同学可以到参考文献中的链接1进行详细的结构学习。结构介绍中所有图片均以BaiduNetdisk.exe
的link文件为样例。)
1. Shell Link Binary File Format
LNK格式可以表达如下:
1 2 | SHELL_LINK = SHELL_LINK_HEADER [LINKTARGET_IDLIST] [LINKINFO]
[STRING_DATA] * EXTRA_DATA
|
SHELL_LINK_HEADER
:一个ShellLinkHeader
结构,包含了确认信息,时间戳,以及指明一些可选结构是否存在的flags。
LINKTARGET_IDLIST
:一个可选的LinkTargetIDList
结构,指定了link的target。
LINKINFO
:一个可选的LinkInfo
结构,指明了处理link target必需的信息。
STRING_DATA
:0个或多个StringData
结构,用于传递用户接口和路径标识信息。
EXTRA_DATA
:0个或多个和ExtraData
结构。
备注:
- Shell Link Binary File Format的结构可以在固定长度字段中定义字符串。 在固定长度的字段中,字符串必须为非null。 如果字符串小于包含它的字段的大小,则终止空字符之后的字段中的字节是不确定的,并且可以具有任何值。未定义的字节不能使用。
- 有一些Shell Link Binary File Format的结构包含size字段,例如
ShellLinkHeader
结构中的HeaderSize
字段,以及LinkInfo
结构中的LinkInfoSize
字段。 除非另有说明,否则这些大小字段包含的值包括大小字段本身的大小。
1. ShellLinkHeader
ShellLinkHeader
是每个shell link文件必须包含的结构,其结构如下:
Offset |
Size(bytes) |
Field |
Description |
0x0000 |
4 |
HeaderSize |
must be 0x0000004c(76) |
0x0004 |
16 |
LinkCLSID |
class identifier(must be 00021401-0000-0000-C000-000000000046) |
0x0014 |
4 |
LinkFlags |
可选和必须结构的相关信息 |
0x0018 |
4 |
FileAttributes |
FileAttributesFlags结构,指定link target的相关信息 |
0x001c |
8 |
CreationTime |
FILETIME结构,指明link target创建时间 |
0x0024 |
8 |
AccessTime |
FILETIME结构,指明link target访问时间 |
0x002c |
8 |
WriteTime |
FILETIME结构,指明link target写时间 |
0x0034 |
4 |
FileSize |
link target的大小,字节为单位。如果link target文件大于0xFFFFFFFF,则此值指定链接目标文件大小的最低有效32位。 |
0x0038 |
4 |
IconIndex |
给定图标位置中图标的索引 |
0x003c |
4 |
ShowCommand |
指定app启动时窗口的状态,须为SW_SHOWNORMAL(0x00000001), SW_SHOWMAXIMIZED(0x00000003),SW_SHOWMINOACTIVE(0x00000007)中的其中一个 |
0x0040 |
2 |
HotKey |
指定启动app的hotkeys |
0x0042 |
2 |
reserved |
MUST be zero |
0x0044 |
4 |
reserved |
MUST be zero |
0x0048 |
4 |
reserved |
MUST be zero |
以上所有结构除特殊说明外,均为小端序。
LinkFlags
字段指定了是否存在可选结构以及各种选项,例如shell link中的字符串是否以Unicode编码,其结构如下:
1 2 3 4 5 | 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|
|
在大多数情况下设置的一个标志HasLinkTargetIDList
由位置“ A”表示,即LinkFlags
字段的第一个字节的最低有效位,如果已设置,则LinkTargetIDList
结构必须紧跟Shell Link Header
。一个例子的该字段的详细结构如下:
2. LinkTargetIDList
LinkTargetIDList
结构指定了link的target,其结构如下:
Offset |
Size |
Field |
Description |
0x0000 |
2 |
IDListSize |
IDList字段的大小,字节为单位 |
0x0002 |
var |
IDList |
包含item ID list的IDList结构 |
如上图所示,首先是IDListSize
结构,然后跟了6个ItemID
结构(详细结构稍后介绍)的IDList
。IDList
数组指定了一个持久化的item ID list的结构:
Offset |
Size |
Field |
Description |
0x0000 |
var(x) |
ItemIDList |
一个ItemID结构的数组 |
0x0000+x |
2 |
TerminalID |
指定item ID的结尾,必须为0 |
ItemIDList
的作用实际上与文件路径的作用相同,其中的每个ItemID
结构对应于类似路径的层次结构中的一个路径组件。ItemID
可以引用实际的文件系统文件夹,例如“控制面板”或“保存的搜索”之类的虚拟文件夹,或充当执行特定功能的“快捷方式”的其他形式的嵌入式数据。
通常情况下,ItemID
的结构如下所示:
Offset |
Size |
Field |
Description |
0x0000 |
2 |
ItemSize |
IDList字段的大小,字节为单位 |
0x0002 |
1 |
ItemType |
Item类型 |
0x0003 |
1 |
Unused |
|
0x0004 |
var |
var |
基于指定的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。
3. Windows中的搜索查询的快捷方式
1. 2种不同的搜索查询
Windows允许用户以2种方式来保存或创建搜索查询的快捷方式。第一种方式是将查询保存为一个XML文件,扩展为".search-ms",该种格式只有一部分内容以文档形式进行了公开。第二种方式是将".search-ms"文件进行序列化然后直接嵌入到LNK文件中,最终的文件包含一个IDList
结构,该结构以一个Delegate Folder ItemID
开始,后面跟一个User Property View ItemID
用于指定搜索查询。
第一种方式:
而打开保存的文件内容可以看到都是xml元素:
第二种方式:
点击前面的搜索图标,会显示保存为一个.search-ms文件,然后按住红框中的图标,直接拖拽到一个路径下,会创建一个LNK文件。而该LNK文件的作用与第一种方式的作用相同,都保存了一样的搜索操作。
查看文件内容,是一个标准的LNK文件:
2. Delegate Folder ItemID
Delegate Folder ItemID
的0x0004偏移处的2个字节是一个表示其余结构的大小的字段,总体结构如下:
Offset |
Size |
Field |
0x0000 |
2 |
ItemSize(n) |
0x0002 |
1 |
ItemType |
0x0003 |
1 |
Unused |
0x0004 |
2 |
DataSize |
0x0006 |
4 |
Signature(0x23a3dfd5) |
0x000a |
2 |
PropertyStoreSize(y) |
0x000c |
2 |
IdentifierSize(x) |
0x000e |
x |
IdentifierData(if x > 0) |
0x000e + x |
y |
PropertyStoreData(if y > 0) |
0x000e + x + y |
6 |
Unkown(0x0) |
0x0014 + x + y |
16 |
Delegate GUID ({5E591A74-DF96-48D3-8D67-1733BCEE28BA}) |
0x0024 + x + y |
16 |
Item GUID |
0x0034 + x + y |
n - (0x0034 + x + y) |
ExtraDataBlock |
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
的引用。
3. User Property View ItemID
Delegate Folder ItemID
后面跟一个User Property View ItemID
,其结构如下:
Offset |
Size |
Field |
0x0000 |
2 |
ItemSize(n) |
0x0002 |
1 |
ItemType |
0x0003 |
1 |
Unused |
0x0004 |
2 |
DataSize |
0x0006 |
4 |
Signature(0x10141981) |
0x000a |
2 |
PropertyStoreSize(y) |
0x000c |
2 |
IdentifierSize(x) |
0x000e |
x |
IdentifierData(if x > 0) |
0x000e + x |
y |
PropertyStoreList(if y > 0) |
0x000e + x + y |
6 |
Unkown(0x0) |
0x0014 + x + y |
n - (0x0014 + x + y)ist |
ExtraDataBlock |
在以上字段中,与漏洞关系比较大的是PropertyStoreList
字段。该字段如果存在,那么会包含一个或多个序列化的PropertyStore
iterm,其结构如下所示:
Offset |
Size |
Field |
0x0000 |
4 |
Store Size |
0x0004 |
4 |
Version("1SPS") |
0x0008 |
16 |
Property Format GUID |
0x0018 |
var |
Property Store Data (sequence) |
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
的一部分,格式如下:
Offset |
Size |
Field |
0x0000 |
4 |
Value Size(x) |
0x0004 |
4 |
Name Size(y) |
0x0008 |
1 |
Reserved(0x00) |
0x0009 |
y |
Name (wide characters) |
0x0009 + y |
x - y - 9 |
TypePropertyValue |
Property bags
通常包含类似名称为"Key:FMTID"或"Key:PID"的内容,这些名称表示了解释其他元素的特定的PKEY
。
如果Property Format GUID
不是前面提到的Property Bags
形式的GUID,那么每个property将会使用一个整数形式的PID
进行标识,其结构如下:
Offset |
Size |
Field |
0x0000 |
4 |
Size(x) |
0x0004 |
4 |
Id |
0x0008 |
1 |
Reserved(0x00) |
0x0009 |
x - 9 |
TypePropertyValue |
TypePropertyvalue
对应于property集中的property的类型值,property集遵循 Microsoft Object Linking and Embedding (OLE) Property Set Data Structures 中的规范(详见参考文献3)。
4. PKEY_FilterInfo
各种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
的结构变成如下形式:
Offset |
Size |
Field |
Description |
0x0000 |
2 |
Property Type |
0x0042, VT_STREAM |
0x0002 |
2 |
Padding |
0x0 |
0x0004 |
4 |
IndirectPropertyName Length |
x |
0x0008 |
x |
IndirectPropertyName |
|
0x0008 + a |
2 |
Padding |
0x0 |
0x000a + x |
4 |
Stream Data Size |
y |
0x000e + x |
y |
Stream Data |
Stream Data
中的数据取决于stream property属于的特定的PKEY
。对于PKEY_FilterInfo
来说,Stream Data
通常包含一个嵌入的PropertyStoreList
,其中包含了多个序列化的PropertyStore
结构。此时,Stream Data
的结构如下所示:
Offset |
Size |
Field |
0x0000 |
4 |
Size(x) |
0x0004 |
4 |
PropertyStoreListSize(y) |
0x0008 |
y |
PropertyStoreList |
0x0008 + y |
4 |
Padding(0x0) |
0x000c + y |
var |
Alignment padding |
PKEY_FilterInfo
stream 中的嵌套PropertyStoreList
实际上是.search-ms
文件中"conditions"标记的序列化版本。下面是一个conditions tag的样例结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <conditions>
<condition type = "andCondition" >
<attributes>
<attribute attributeID = "{9554087B-CEB6-45AB-99FF-50E8428E860D}" clsid = "{C64B9B66-E53D-4C56-B9AE-FEDE4EE95DB1}" chs = "1" sqro = "585" timestamp_low = "2637566744" timestamp_high = "30796574" >
<condition type = "andCondition" >
<attributes>
<attribute attributeID = "{9554087B-CEB6-45AB-99FF-50E8428E860D}" clsid = "{C64B9B66-E53D-4C56-B9AE-FEDE4EE95DB1}" chs = "0" parsedString = "size:500..1000 blah" localeName = "en-US" timestamp_low = "2555883479" timestamp_high = "30796574" / >
< / attributes>
<condition type = "leafCondition" property = "System.Size" operator = "imp" propertyType = "stringarray" value = "=500; =1000" valuetype = "System.StructuredQueryType.Integer" localeName = "en-US" >
<attributes / >
< / condition>
<condition type = "leafCondition" property = " " operator=" imp " propertyType=" string " value=" blah " localeName=" en - US">
<attributes / >
< / condition>
< / condition>
< / attribute>
< / attributes>
[......]
< / condition>
< / conditions>
|
"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的结构如下:
Offset |
Size |
Field |
0x0000 |
4 |
Value Size(x) |
0x0004 |
4 |
Name Size(0x14) |
0x0008 |
1 |
Reserved(0x00) |
0x0009 |
20 |
Name("Condition") |
0x001d |
2 |
Property Type(0x0042 VT_STREAM) |
0x001f |
2 |
Padding(0x0) |
0x0021 |
4 |
Indirect Propname Length (y) |
0x0025 |
y |
Indirect Propname (prop *) |
0x0025 + y |
var |
Condition object |
Condition object
通常是一个Leaf Condition
或者Compound Condition
对象,其中可以包含嵌套对象,通常包含一个或多个Leaf Condition
,也可能包含其他的Compound Condition
对象。2种condition object都具有以下结构:
Offset |
Size |
Field |
0x0000 |
4 |
Condition size (x) |
0x0004 |
16 |
Condition GUID |
0x0014 |
4 |
Number of Attributes |
0x0018 |
var |
Attributes |
对于Leaf Condition
来说,其Conditioin GUID
为{52F15C89-5A17-48E1-BBCD-46A3F89C7CC2}
;对于Compound Condition
来说,其Conditioin GUID
为{116F8D13-101E-4FA5-84D4-FF8279381935}
。Attributes
字段由几个Attributes
attribute结构组成,每个对应于之前提到的search-ms文件中的attribute标签,字段结构如下:
Offset |
Size |
Field |
0x0000 |
16 |
AttributeID |
0x0010 |
16 |
Attribute CLSID |
Attribute
的其他结构取决于AttributeID
和CLSID
。对于前面的attributeID
为{9554087B-CEB6-45AB-99FF-50E8428E860D}
,CLSID
为{C64B9B66-E53D-4C56-B9AE-FEDE4EE95DB1}
的CONDIOTION_HISTORY
attribute来说,其剩余结构为一个ConditionHistory
对象,该对象结构如下:
Offset |
Size |
Field |
0x0000 |
4 |
chs |
0x0004 |
4 |
parseStringWChars (x = value - 1) |
0x0008 |
x * 2 |
parseString |
0x0008 + (x * 2) |
4 |
localeStringWChars (y = value -1) |
0x000c + (x * 2) |
y * 2 |
localeString |
0x000c + (x 2) + (y 2) |
4 |
sqro |
0x0010 + (x 2) + (y 2) |
4 |
unknown |
0x0014 + (x 2) + (y 2) |
8 |
timestamp |
0x001c + (x 2) + (y 2) |
4 |
nested_condition (may be number of directly nested conditions) |
0x0020 + (x 2) + (y 2) |
var |
Condition object( if nested_condition > 0) |
如果nested_condition
的值大于0,CONDITION_HISTORY
就会有嵌套的condition object,该对象本身也可能具有具有嵌套条件的嵌套属性,依此类推。
在完全读取了所有的嵌套结构后,Compoound Condition
和Leaf Condition
结构后续就不同了。紧接Attributes
字段后面,Compound Condition
的结构如下:
Offset |
Size |
Field |
0x0000 |
4 |
Unknown(通常为0x0) |
0x0004 |
4 |
numFixedObjects |
0x0008 |
var |
Conditions( if numFixedObjects > 0) |
numFixedObjects
字段指明了后续会跟多少附加conditions(通常为Leaf Conditions
)。
而对于Leaf Condition
,其结构如下:
Offset |
Size |
Field |
0x0000 |
16 |
Condition Type PKEY GUID |
0x0010 |
4 |
Conditioin Type Property ID |
0x0014 |
4 |
Condition Operation(CONDITION_OPERATION enum) |
0x0018 |
var |
PropertyVariant |
var |
4 |
valuetypeStringWChars (x = value - 1) |
var |
x * 2 |
valueTypeString |
var |
4 |
localeStringWChars (y = value -1) |
var |
y * 2 |
localeString |
var |
var |
TokenInfomationComplete1 flag |
var |
4 |
TokenInfomationComplete1 ( if flag = 0x1 or 0x2) |
var |
var |
TokenInfomationComplete2 flag |
var |
4 |
TokenInfomationComplete2 ( if flag = 0x1 or 0x2) |
var |
var |
TokenInfomationComplete3 |
var |
4 |
TokenInfomationComplete3 ( if flag = 0x1 or 0x2) |
TokenInfomationComplete
结构存在与否取决于前面的flag是否进行了设置,如果没有设置那么紧跟下一个flag;如果进行了设置,那么紧跟的结构如下:
Offset |
Size |
Field |
0x0000 |
4 |
Unknown |
0x0004 |
4 |
Unknown |
0x0008 |
4 |
numWChars (x = value - 1) |
0x000c |
x * 2 |
String |
0x000c + (x * 2) |
4 |
Value (flag = 2时才存在) |
对于Leaf Condition
的PropertyVariant
字段,其结构大致与PROPVARIANT
结构(参考文献5)相似。PropertyVariant
结构包含2字节的type,其后为该type的data。重要的是要注意,由于参考文献5中指定的填充字节往往不存在,因此StructuredQuery
似乎对PROPVARIANT
结构进行了自定义实现。 还需要注意的是,值0x1000或VT_VECTOR
与另一种类型结合使用,意味着将有多个指定类型的值。
简述一个具有保存的搜哦的LNK文件的最简结构如下所示(去除无关结构):
以上只是一个最简单的使用单个Leaf Condition
结构的搜索LNK结构,真实情况种往往是从Compund Condition
开始,别切包含很多嵌套结构(包括许多的Leaf Condition
)。
3. 详细分析
1. 漏洞触发流程
造成该漏洞的主要原因是在处理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(.LNK) Binary File Format
- Common Explorer Concepts, Identifying Namespace Objects)
- MS-OLEPS: TypedPropertyValue
- MS-OLEPS: IndirectPropertyName
- Structured Storage, Propidl.h, PROPVARIANT structure
(未完待续)
大家稍等哈,最近有活动,实在是忙不开,到时候我看看直接把震网的也一起加进来,抱歉
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2020-9-27 20:17
被有毒编辑
,原因: