-
-
[原创]Windows NTFS本地提权漏洞 CVE-2021-31956
-
发表于: 2023-6-28 18:05 12088
-
参考链接:
92aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5k6i4y4W2j5i4u0U0K9q4)9J5k6h3&6U0j5$3N6J5L8%4g2H3i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9J5x3g2)9J5c8U0l9%4i4K6u0r3x3e0g2Q4x3V1k6U0N6X3g2Q4x3X3b7J5x3o6t1I4i4K6u0V1x3K6p5&6y4e0k6Q4x3X3c8W2P5s2m8D9L8$3W2@1K9h3&6Y4i4K6u0V1N6r3S2W2i4K6u0V1N6$3W2F1k6r3!0%4M7#2)9J5k6r3E0W2M7X3&6W2L8q4)9J5k6r3&6@1k6Y4y4Q4x3X3c8%4K9i4c8Z5i4K6u0V1N6$3&6X3i4K6u0V1M7r3q4J5N6q4)9J5k6o6q4Q4x3V1j5`.
8a3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5k6i4y4W2j5i4u0U0K9q4)9J5k6h3&6U0j5$3N6J5L8%4g2H3i4K6u0W2j5$3!0E0i4K6u0r3x3U0l9J5x3g2)9J5c8U0l9^5i4K6u0r3x3e0N6Q4x3V1k6U0N6X3g2Q4x3X3b7J5x3o6t1I4i4K6u0V1x3K6p5&6y4e0k6Q4x3X3c8W2P5s2m8D9L8$3W2@1K9h3&6Y4i4K6u0V1N6r3S2W2i4K6u0V1N6$3W2F1k6r3!0%4M7#2)9J5k6r3E0W2M7X3&6W2L8q4)9J5k6r3&6@1k6Y4y4Q4x3X3c8%4K9i4c8Z5i4K6u0V1N6$3&6X3i4K6u0V1M7r3q4J5N6q4)9J5k6o6u0Q4x3V1j5`.
ee4K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5i4m8W2M7W2)9J5k6i4y4W2k6h3u0#2k6#2)9J5k6h3!0J5k6#2)9J5c8U0p5%4y4o6x3`.
使用PipeAttribution构造任意地址读后,修改_WNF_NAME_INSTANCE结构体内的指针_WNF_STATE_DATA实现任意地址写
047K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1j5i4N6F1M7$3I4S2j5W2)9J5k6h3A6V1i4K6u0W2j5$3!0E0i4K6u0r3b7#2k6q4i4K6u0V1x3U0l9J5x3g2)9J5k6o6x3I4z5e0f1$3i4K6u0r3
b32K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Z5P5Y4y4Z5j5h3&6Y4i4K6u0r3b7#2k6q4i4K6u0V1x3U0l9J5x3g2)9J5k6o6x3I4z5e0f1$3
使用使用NtQueryWnfStateData和NtUpDateWnfStateData API来造成任意地址的读写(需要构造AllocateSize和DataSize成员)
https://bbs.kanxue.com/thread-271140.htm
928K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2j5i4A6Z5N6h3I4A6j5h3&6Y4i4K6u0r3b7#2k6q4i4K6u0V1x3U0l9J5x3g2)9J5k6o6x3I4z5e0f1$3i4K6u0V1c8g2S2b7
漏洞在windows的NTFS文件系统驱动上(C:\Windows\System32\drivers\ntfs.sys)的NtfsQueryEaUserEaList函数中
NTFS文件系统允许为每一个文件额外存储若干个键值对属性,称之为EA(Extend Attribution) 。可以通过ZwSetEaFile为文件创建EA,ZwQueryEaFile查询文件EA
泄露的NT5.1中有NtfsQueryEaUserEaList的源码
NtfsQueryEaUserEaList从 循环遍历文件的每个 NTFS 扩展属性 (Ea),并根据ea_block->EaValueLength + ea_block->EaNameLength + 9的大小从 Ea 块复制到输出缓冲区。
有一个检查确保ea_block_size小于或等于out_buf_length - padding。然后,out_buf_length会减去ea_block_size及其填充的大小。填充是通过((ea_block_size + 3) 0xFFFFFFFC) - ea_block_size来计算的。因为每个EA块应该填充为32位对齐。
假设文件的扩展属性中有两个扩展属性
第一次迭代
因此18 < out_buf_length - 0,数据将被复制到缓冲区中
第二次迭代
在文件中添加一个具有相同值的第二个扩展属性。
由于缓冲区太小,第二次内存复制将不会发生。
第一次迭代
检查结果为:
18 <= 18 - 0 // is True and a copy of 18 occurs.
第二个扩展属性具有以下值:
结果检查将是:
ea_block_size <= out_buf_length - padding
137 <= 0 - 2
发生下溢,137 个字节将被复制到缓冲区末尾,从而损坏相邻内存。
查看NtfsQueryEaUserEaList函数的调用者NtfsCommonQueryEa,我们可以看到输出缓冲区是根据请求的大小在分页池上分配的
NtfsCommonQueryEa函数可通过ZwQueryEaFIle函数调用
可以看到输出缓冲区Buffer以及该缓冲区的长度都是从用户空间传入的。这意味着我们根据缓冲区的大小控制内核空间的内存分配。
该漏洞对攻击者来说:
溢出拷贝时数据和大小均可控。
可以覆盖下一个内核池块
内核池分配时大小可控,并且可以进行堆布局。
Windows10引入了新的方式进行堆块管理,称为Segment Heap,具体可看以下论文
716K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2K6M7%4c8A6j5#2)9J5k6h3!0J5k6#2)9J5c8X3#2W2k6r3W2S2i4K6u0r3f1#2y4f1d9f1x3J5x3o6t1H3i4K6u0r3f1#2y4f1d9f1y4Q4x3X3c8S2j5%4c8W2M7#2)9J5c8Y4m8G2L8$3I4Q4y4h3k6G2N6X3g2J5k6X3I4G2N6#2)9#2k6X3g2^5M7r3I4G2K9i4c8S2N6r3W2G2L8W2)9#2k6Y4y4A6L8X3y4W2i4K6g2X3N6$3W2F1k6r3!0%4M7#2)9#2k6U0p5H3i4K6g2X3x3e0W2Z5x3g2)9J5c8W2y4e0g2p5W2o6x3U0l9J5x3q4)9J5k6p5q4J5N6r3W2U0L8r3g2Q4x3X3c8H3L8$3!0D9i4K6g2X3L8%4k6W2M7X3k6D9L8%4N6Q4y4h3k6W2P5s2m8D9L8$3W2@1j5i4c8A6L8$3&6Q4y4h3k6K6K9h3&6U0k6g2)9#2k6Y4N6A6L8X3c8G2N6%4y4Q4y4h3j5I4x3q4)9#2k6U0p5&6K9o6q4Q4x3X3c8T1j5i4W2W2N6q4)9#2k6X3k6S2M7X3W2W2L8r3I4G2i4K6u0W2M7r3c8X3
EXP:0a2K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6S2j5i4A6Z5N6h3I4A6j5h3&6Y4i4K6u0r3b7#2k6q4i4K6u0V1x3U0l9J5x3g2)9J5k6o6x3I4z5e0f1$3i4K6u0V1c8g2S2b7
仿照EXP改的可以触发溢出的POC
可以看到整数下溢
Windows10引入了新的方式进行堆块管理,称为Segment Heap
详见:129K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2K6M7%4c8A6j5#2)9J5k6h3!0J5k6#2)9J5c8X3#2W2k6r3W2S2i4K6u0r3f1#2y4f1d9f1x3J5x3o6t1H3i4K6u0r3f1#2y4f1d9f1y4Q4x3X3c8S2j5%4c8W2M7#2)9J5c8Y4m8G2L8$3I4Q4y4h3k6G2N6X3g2J5k6X3I4G2N6#2)9#2k6X3g2^5M7r3I4G2K9i4c8S2N6r3W2G2L8W2)9#2k6Y4y4A6L8X3y4W2i4K6g2X3N6$3W2F1k6r3!0%4M7#2)9#2k6U0p5H3i4K6g2X3x3e0W2Z5x3g2)9J5c8W2y4e0g2p5W2o6x3U0l9J5x3q4)9J5k6p5q4J5N6r3W2U0L8r3g2Q4x3X3c8H3L8$3!0D9i4K6g2X3L8%4k6W2M7X3k6D9L8%4N6Q4y4h3k6W2P5s2m8D9L8$3W2@1j5i4c8A6L8$3&6Q4y4h3k6K6K9h3&6U0k6g2)9#2k6Y4N6A6L8X3c8G2N6%4y4Q4y4h3j5I4x3q4)9#2k6U0p5&6K9o6q4Q4x3X3c8T1j5i4W2W2N6q4)9#2k6X3k6S2M7X3W2W2L8r3I4G2i4K6u0W2M7r3c8X3
中文翻译:439K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5i4m8W2M7W2)9J5k6i4y4W2k6h3u0#2k6#2)9J5k6h3!0J5k6#2)9J5c8U0p5%4y4o6x3`.
POOL_HEADER 在池中,适合单个页面的所有块都以POOL_HEADER结构开头,POOL_HEADER包含分配器所需信息和Tag信息。当试图在Windows内核中利用堆溢出漏洞时,首先要覆盖的就是POOL_HEADER结构。攻击者有两个选择:重写一个正确的POOL_HEADER结构,并用来攻击下一个块的数据,或者直接攻击POOL_HEADER结构。
PoolType是一个位域,存储若干信息:
使用的内存类型,可以是NonPagedPool、PagedPool、SessionPool或NonPagedPoolNx;
如果分配是关键的(bit 1)并且必须成功。那么当分配失败,就会触发BugCheck;
如果分配与缓存大小对齐(bit 2)
如果分配使用了PoolQuota机制(bit 3)
其他未文档化的机制
WNF Windows Notification Facitily 是 Windows 中的一个通知系统。应用程序可以订阅特定类型的事件(StateName标识),在每次状态更改时可以进行通知。
WNF 利用相关文章
60bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1L8$3y4H3L8r3q4&6k6i4u0Q4x3X3g2F1k6i4c8Q4x3V1j5I4y4o6f1H3x3K6l9^5y4o6q4Q4x3X3c8f1K9r3g2Q4x3X3c8%4K9h3&6V1L8%4N6K6i4K6u0V1L8X3!0@1K9h3k6A6j5$3q4@1K9h3!0F1i4K6u0V1k6X3q4U0K9h3I4A6N6s2W2Q4x3X3g2Z5N6r3#2D9
de7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2M7i4g2S2M7X3E0K6L8r3q4T1i4K6u0W2j5$3!0E0i4K6u0r3M7r3I4S2P5h3W2F1k6#2)9J5k6s2N6A6N6r3S2Q4x3X3c8@1K9r3g2Q4x3X3c8%4K9h3&6V1L8%4N6K6i4K6u0V1L8X3!0@1K9h3k6A6j5$3q4@1K9h3!0F1i4K6u0V1k6X3q4U0K9h3I4A6N6s2W2Q4x3X3c8%4L8X3k6Q4x3X3g2Z5N6r3#2D9
结构体_WNF_STATE_DATA大可以由用户自定义
用户可以通过NtCreateWnfStateName创建一个WNF对象实例,实例的数据结构为_WNF_NAME_INSTANCE;WNF对象大小为0xb8(WNF_NAME_INSTANCE + POOL_HEADER )
NtUpdateWnfStateData可以往对象里写入数据,使用_WNF_STATE_DATA结构存储写入的内容;
通过NtQueryWnfStateData可以读取之前写入的数据,通过NtDeleteWnfStateData可以释放掉这个对象。NtDeleteWnfStateDat会调用ExpWnfDeleteStateData。
DataSize表示内存中_WNF_STATE_DATA结构的实际数据的大小,并用于NtQueryWnfStateData函数内的边界检查。_WNF_STATE_DATA结构存储写入的内容的内存复制操作发生在函数ExpWnfReadStateData中。
通过堆喷控制内存,使用NTFS的堆溢出越界写_WNF_STATE_DATA中的DataSize,接下来通过NtQueryWnfStateData实现相对偏移地址读写。
PipeAttribute块的大小也是可控的,并且分配在分页池上,因此可以将块放置在与易受攻击的 NTFS 块或允许相对写入的 WNF 块相邻的位置。两个指针AttributeName、AttributeValue 正常情况下是指向PipeAttribute.data[]后面的,通过堆布局,将AttributeValue的指针该为任意地址,就可以实现任意地址读。遗憾的是,windows并没有提供直接更新该数据结构的功能,不能通过该方法进行任意地址写。
使用这个布局,修改PipeAttribute的Flink指针,并将其指向一个伪造的管道属性。
// 使指向下一个属性的指针在用户层
overwritten_pipe_attribute->list.Flink = (LIST_ENTRY *)xploit->fake_pipe_attribute;
分页池创建管道后,用户可以向管道添加属性,同时属性值分配的大小和填充的数据完全由用户来控制。
AttributeName和AttributeValue是指向数据区不同偏移的两个指针。
同时在用户层,可以使用0x110038控制码来读取属性值。AttributeValue指针和AttributeValueSize大小将被用于读取属性值并返回给用户。
属性值可以被修改,但这会触发先前的PipeAttribute的释放和新的PipeAttribute的分配。这意味着如果攻击者可以控制PipeAttribute结构体的AttributeValue和AttributeValueSize字段,它就可以在内核中任意读取数据,但不能任意写。
所以,控制Pipe_Attribute的List_next指针值,使其指向用户层的Pipe_Attribute,也就意味着用户层的PipeAttribute结构体的AttributeValue和AttributeValueSize字段我们可以任意指定,也就可以在内核中任意读取数据数据,即获得了一个任意地址读原语。
释放掉堆喷未修改的其他的Pipe_Attribute结构,使用_WNF_NAME_INSTANCE重新进行堆喷,通过局部地址读写,覆盖掉下一个Wnf结构体里的_WNF_STATE_DATA,将其指向当前进程的EPROCESS,使用NtUpdateWnfStateData操作,即可实现写操作 。
_WNF_NAME_INSTANCE结构的CreatorProcess包含_EPROCESS指针
遍历进程链表,获得pid4的token
修改 WNF的StateData指向当前进程的token
调用NtUpdateWnfStateData替换当前进程的token为 system的
在卡巴发现的在野利用样本中,CVE-2021-31956利用了CVE-2021-31955来解决EPROCESS地址泄漏问题。
CVE-2021-31955漏洞是ntoskrnl.exe中的一个信息泄露漏洞。NtQuerySystemInformation 函数返回的 SuperFetch信息类SuperfetchPrivSourceQuery中包含当前执行的进程的EPROCESS kernel 地址。
078K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6X3M7X3g2W2K9h3c8W2i4K6u0r3b7#2k6q4i4K6u0V1x3U0l9J5x3g2)9J5k6o6x3I4z5e0f1#2i4K6u0V1f1p5!0o6
typedef struct _FILE_GET_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR EaNameLength;
CHAR EaName[
1
];
} FILE_GET_EA_INFORMATION,
*
PFILE_GET_EA_INFORMATION;
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[
1
];
} FILE_FULL_EA_INFORMATION,
*
PFILE_FULL_EA_INFORMATION;
typedef struct _FILE_GET_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR EaNameLength;
CHAR EaName[
1
];
} FILE_GET_EA_INFORMATION,
*
PFILE_GET_EA_INFORMATION;
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset;
UCHAR Flags;
UCHAR EaNameLength;
USHORT EaValueLength;
CHAR EaName[
1
];
} FILE_FULL_EA_INFORMATION,
*
PFILE_FULL_EA_INFORMATION;
IO_STATUS_BLOCK
NtfsQueryEaUserEaList(
IN PFILE_FULL_EA_INFORMATION CurrentEas,
IN PEA_INFORMATION EaInformation,
OUT PFILE_FULL_EA_INFORMATION EaBuffer,
IN ULONG UserBufferLength,
IN PFILE_GET_EA_INFORMATION UserEaList,
IN BOOLEAN ReturnSingleEntry
)
/
*
+
+
Routine Description:
This routine
is
the work routine
for
querying EAs given a
list
of Ea's to search
for
.
Arguments:
CurrentEas
-
This
is
a pointer to the current Eas
for
the
file
EaInformation
-
This
is
a pointer to an Ea information attribute.
EaBuffer
-
Supplies the
buffer
to receive the full eas
UserBufferLength
-
Supplies the length,
in
bytes, of the user
buffer
UserEaList
-
Supplies the user specified ea name
list
ReturnSingleEntry
-
Indicates
if
we are to
return
a single entry
or
not
Return Value:
IO_STATUS_BLOCK
-
Receives the completion status
for
the operation
-
-
*
/
{
IO_STATUS_BLOCK Iosb;
ULONG GeaOffset;
ULONG FeaOffset;
ULONG Offset;
PFILE_FULL_EA_INFORMATION LastFullEa;
PFILE_FULL_EA_INFORMATION NextFullEa;
PFILE_GET_EA_INFORMATION GetEa;
BOOLEAN Overflow;
ULONG PrevEaPadding;
PAGED_CODE();
DebugTrace(
+
1
, Dbg, (
"NtfsQueryEaUserEaList: Entered\n"
));
/
/
/
/
Setup pointer
in
the output
buffer
so we can track the Ea being
/
/
written to it
and
the last Ea written.
/
/
LastFullEa
=
NULL;
Overflow
=
FALSE;
/
/
/
/
Initialize our
next
offset value.
/
/
GeaOffset
=
0
;
Offset
=
0
;
PrevEaPadding
=
0
;
/
/
/
/
Loop through
all
the entries
in
the user's ea
list
.
/
/
while
(TRUE) {
STRING GeaName;
STRING OutputEaName;
ULONG RawEaSize;
/
/
/
/
Get the
next
entry
in
the user's
list
.
/
/
GetEa
=
(PFILE_GET_EA_INFORMATION)Add2Ptr(UserEaList, GeaOffset);
/
/
/
/
Make a string reference to the name
and
see
if
we can locate
/
/
the ea by name.
/
/
GeaName.MaximumLength
=
GeaName.Length
=
GetEa
-
>EaNameLength;
GeaName.
Buffer
=
&GetEa
-
>EaName[
0
];
/
/
/
/
Upcase the name so we can do a case
-
insensitive compare.
/
/
NtfsUpcaseEaName(&GeaName, &GeaName);
/
/
/
/
Check
for
a valid name.
/
/
if
(!NtfsIsEaNameValid(GeaName)) {
DebugTrace(
-
1
, Dbg, (
"NtfsQueryEaUserEaList: Invalid Ea Name\n"
));
Iosb.Information
=
GeaOffset;
Iosb.Status
=
STATUS_INVALID_EA_NAME;
return
Iosb;
}
GeaOffset
+
=
GetEa
-
>NextEntryOffset;
/
/
/
/
If this
is
a duplicate name, then step over this entry.
/
/
if
(NtfsIsDuplicateGeaName(GetEa, UserEaList)) {
/
/
/
/
If we've exhausted the entries
in
the Get Ea
list
, then we are
/
/
done.
/
/
if
(GetEa
-
>NextEntryOffset
=
=
0
) {
break
;
}
else
{
continue
;
}
}
/
/
/
/
Generate a pointer
in
the Ea
buffer
.
/
/
NextFullEa
=
(PFILE_FULL_EA_INFORMATION)Add2Ptr(EaBuffer, Offset
+
PrevEaPadding);
/
/
/
/
Try to find a matching Ea.
/
/
If we couldn
't, let'
s dummy up an Ea to give to the user.
/
/
if
(!NtfsLocateEaByName(CurrentEas,
EaInformation
-
>UnpackedEaSize,
&GeaName,
&FeaOffset)) {
/
/
/
/
We were
not
able to locate the name therefore we must
/
/
dummy up a entry
for
the query. The needed Ea size
is
/
/
the size of the name
+
4
(
next
entry offset)
+
1
(flags)
/
/
+
1
(name length)
+
2
(value length)
+
the name length
+
/
/
1
(null byte).
/
/
RawEaSize
=
4
+
1
+
1
+
2
+
GetEa
-
>EaNameLength
+
1
;
if
((RawEaSize
+
PrevEaPadding) > UserBufferLength) {
Overflow
=
TRUE;
break
;
}
/
/
/
/
Everything
is
going to work fine, so copy over the name,
/
/
set
the name length
and
zero out the rest of the ea.
/
/
NextFullEa
-
>NextEntryOffset
=
0
;
NextFullEa
-
>Flags
=
0
;
NextFullEa
-
>EaNameLength
=
GetEa
-
>EaNameLength;
NextFullEa
-
>EaValueLength
=
0
;
RtlCopyMemory(&NextFullEa
-
>EaName[
0
],
&GetEa
-
>EaName[
0
],
GetEa
-
>EaNameLength);
/
/
/
/
Upcase the name
in
the
buffer
.
/
/
OutputEaName.MaximumLength
=
OutputEaName.Length
=
GeaName.Length;
OutputEaName.
Buffer
=
NextFullEa
-
>EaName;
NtfsUpcaseEaName(&OutputEaName, &OutputEaName);
NextFullEa
-
>EaName[GetEa
-
>EaNameLength]
=
0
;
/
/
/
/
Otherwise
return
the Ea we found back to the user.
/
/
}
else
{
PFILE_FULL_EA_INFORMATION ThisEa;
/
/
/
/
Reference this ea.
/
/
ThisEa
=
(PFILE_FULL_EA_INFORMATION)Add2Ptr(CurrentEas, FeaOffset);
/
/
/
/
Check
if
this Ea can fit
in
the user's
buffer
.
/
/
RawEaSize
=
RawUnpackedEaSize(ThisEa);
if
(RawEaSize > (UserBufferLength
-
PrevEaPadding)) {
Overflow
=
TRUE;
break
;
}
/
/
/
/
Copy this ea to the user's
buffer
.
/
/
RtlCopyMemory(NextFullEa,
ThisEa,
RawEaSize);
NextFullEa
-
>NextEntryOffset
=
0
;
}
/
/
/
/
Compute the
next
offset
in
the user's
buffer
.
/
/
Offset
+
=
(RawEaSize
+
PrevEaPadding);
/
/
/
/
If we were to
return
a single entry then
break
out of our loop
/
/
now
/
/
if
(ReturnSingleEntry) {
break
;
}
/
/
/
/
If we have a new Ea entry, go back
and
update the offset field
/
/
of the previous Ea entry.
/
/
if
(LastFullEa !
=
NULL) {
LastFullEa
-
>NextEntryOffset
=
PtrOffset(LastFullEa, NextFullEa);
}
/
/
/
/
If we've exhausted the entries
in
the Get Ea
list
, then we are
/
/
done.
/
/
if
(GetEa
-
>NextEntryOffset
=
=
0
) {
break
;
}
/
/
/
/
Remember this as the previous ea value. Also update the
buffer
/
/
length values
and
the
buffer
offset values.
/
/
LastFullEa
=
NextFullEa;
UserBufferLength
-
=
(RawEaSize
+
PrevEaPadding);
/
/
/
/
Now remember the padding bytes needed
for
this call.
/
/
PrevEaPadding
=
LongAlign(RawEaSize)
-
RawEaSize;
}
/
/
/
/
If the Ea information won
't fit in the user'
s
buffer
, then
return
/
/
an overflow status.
/
/
if
(Overflow) {
Iosb.Information
=
0
;
Iosb.Status
=
STATUS_BUFFER_OVERFLOW;
/
/
/
/
Otherwise
return
the length of the data returned.
/
/
}
else
{
/
/
/
/
Return the length of the
buffer
filled
and
a success
/
/
status.
/
/
Iosb.Information
=
Offset;
Iosb.Status
=
STATUS_SUCCESS;
}
DebugTrace(
0
, Dbg, (
"Status -> %08lx\n"
, Iosb.Status));
DebugTrace(
0
, Dbg, (
"Information -> %08lx\n"
, Iosb.Information));
DebugTrace(
-
1
, Dbg, (
"NtfsQueryEaUserEaList: Exit\n"
));
return
Iosb;
}
IO_STATUS_BLOCK
NtfsQueryEaUserEaList(
IN PFILE_FULL_EA_INFORMATION CurrentEas,
IN PEA_INFORMATION EaInformation,
OUT PFILE_FULL_EA_INFORMATION EaBuffer,
IN ULONG UserBufferLength,
IN PFILE_GET_EA_INFORMATION UserEaList,
IN BOOLEAN ReturnSingleEntry
)
/
*
+
+
Routine Description:
This routine
is
the work routine
for
querying EAs given a
list
of Ea's to search
for
.
Arguments:
CurrentEas
-
This
is
a pointer to the current Eas
for
the
file
EaInformation
-
This
is
a pointer to an Ea information attribute.
EaBuffer
-
Supplies the
buffer
to receive the full eas
UserBufferLength
-
Supplies the length,
in
bytes, of the user
buffer
UserEaList
-
Supplies the user specified ea name
list
ReturnSingleEntry
-
Indicates
if
we are to
return
a single entry
or
not
Return Value:
IO_STATUS_BLOCK
-
Receives the completion status
for
the operation
-
-
*
/
{
IO_STATUS_BLOCK Iosb;
ULONG GeaOffset;
ULONG FeaOffset;
ULONG Offset;
PFILE_FULL_EA_INFORMATION LastFullEa;
PFILE_FULL_EA_INFORMATION NextFullEa;
PFILE_GET_EA_INFORMATION GetEa;
BOOLEAN Overflow;
ULONG PrevEaPadding;
PAGED_CODE();
DebugTrace(
+
1
, Dbg, (
"NtfsQueryEaUserEaList: Entered\n"
));
/
/
/
/
Setup pointer
in
the output
buffer
so we can track the Ea being
/
/
written to it
and
the last Ea written.
/
/
LastFullEa
=
NULL;
Overflow
=
FALSE;
/
/
/
/
Initialize our
next
offset value.
/
/
GeaOffset
=
0
;
Offset
=
0
;
PrevEaPadding
=
0
;
/
/
/
/
Loop through
all
the entries
in
the user's ea
list
.
/
/
while
(TRUE) {
STRING GeaName;
STRING OutputEaName;
ULONG RawEaSize;
/
/
/
/
Get the
next
entry
in
the user's
list
.
/
/
GetEa
=
(PFILE_GET_EA_INFORMATION)Add2Ptr(UserEaList, GeaOffset);
/
/
/
/
Make a string reference to the name
and
see
if
we can locate
/
/
the ea by name.
/
/
GeaName.MaximumLength
=
GeaName.Length
=
GetEa
-
>EaNameLength;
GeaName.
Buffer
=
&GetEa
-
>EaName[
0
];
/
/
/
/
Upcase the name so we can do a case
-
insensitive compare.
/
/
NtfsUpcaseEaName(&GeaName, &GeaName);
/
/
/
/
Check
for
a valid name.
/
/
if
(!NtfsIsEaNameValid(GeaName)) {
DebugTrace(
-
1
, Dbg, (
"NtfsQueryEaUserEaList: Invalid Ea Name\n"
));
Iosb.Information
=
GeaOffset;
Iosb.Status
=
STATUS_INVALID_EA_NAME;
return
Iosb;
}
GeaOffset
+
=
GetEa
-
>NextEntryOffset;
/
/
/
/
If this
is
a duplicate name, then step over this entry.
/
/
if
(NtfsIsDuplicateGeaName(GetEa, UserEaList)) {
/
/
/
/
If we've exhausted the entries
in
the Get Ea
list
, then we are
/
/
done.
/
/
if
(GetEa
-
>NextEntryOffset
=
=
0
) {
break
;
}
else
{
continue
;
}
}
/
/
/
/
Generate a pointer
in
the Ea
buffer
.
/
/
NextFullEa
=
(PFILE_FULL_EA_INFORMATION)Add2Ptr(EaBuffer, Offset
+
PrevEaPadding);
/
/
/
/
Try to find a matching Ea.
/
/
If we couldn
't, let'
s dummy up an Ea to give to the user.
/
/
if
(!NtfsLocateEaByName(CurrentEas,
EaInformation
-
>UnpackedEaSize,
&GeaName,
&FeaOffset)) {
/
/
/
/
We were
not
able to locate the name therefore we must
/
/
dummy up a entry
for
the query. The needed Ea size
is
/
/
the size of the name
+
4
(
next
entry offset)
+
1
(flags)
/
/
+
1
(name length)
+
2
(value length)
+
the name length
+
/
/
1
(null byte).
/
/
RawEaSize
=
4
+
1
+
1
+
2
+
GetEa
-
>EaNameLength
+
1
;
if
((RawEaSize
+
PrevEaPadding) > UserBufferLength) {
Overflow
=
TRUE;
break
;
}
/
/
/
/
Everything
is
going to work fine, so copy over the name,
/
/
set
the name length
and
zero out the rest of the ea.
/
/
NextFullEa
-
>NextEntryOffset
=
0
;
NextFullEa
-
>Flags
=
0
;
NextFullEa
-
>EaNameLength
=
GetEa
-
>EaNameLength;
NextFullEa
-
>EaValueLength
=
0
;
RtlCopyMemory(&NextFullEa
-
>EaName[
0
],
&GetEa
-
>EaName[
0
],
GetEa
-
>EaNameLength);
/
/
/
/
Upcase the name
in
the
buffer
.
/
/
OutputEaName.MaximumLength
=
OutputEaName.Length
=
GeaName.Length;
OutputEaName.
Buffer
=
NextFullEa
-
>EaName;
NtfsUpcaseEaName(&OutputEaName, &OutputEaName);
NextFullEa
-
>EaName[GetEa
-
>EaNameLength]
=
0
;
/
/
/
/
Otherwise
return
the Ea we found back to the user.
/
/
}
else
{
PFILE_FULL_EA_INFORMATION ThisEa;
/
/
/
/
Reference this ea.
/
/
ThisEa
=
(PFILE_FULL_EA_INFORMATION)Add2Ptr(CurrentEas, FeaOffset);
/
/
/
/
Check
if
this Ea can fit
in
the user's
buffer
.
/
/
RawEaSize
=
RawUnpackedEaSize(ThisEa);
if
(RawEaSize > (UserBufferLength
-
PrevEaPadding)) {
Overflow
=
TRUE;
break
;
}
/
/
/
/
Copy this ea to the user's
buffer
.
/
/
RtlCopyMemory(NextFullEa,
ThisEa,
RawEaSize);
NextFullEa
-
>NextEntryOffset
=
0
;
}
/
/
/
/
Compute the
next
offset
in
the user's
buffer
.
/
/
Offset
+
=
(RawEaSize
+
PrevEaPadding);
/
/
/
/
If we were to
return
a single entry then
break
out of our loop
/
/
now
/
/
if
(ReturnSingleEntry) {
break
;
}
/
/
/
/
If we have a new Ea entry, go back
and
update the offset field
/
/
of the previous Ea entry.
/
/
if
(LastFullEa !
=
NULL) {
LastFullEa
-
>NextEntryOffset
=
PtrOffset(LastFullEa, NextFullEa);
}
/
/
/
/
If we've exhausted the entries
in
the Get Ea
list
, then we are
/
/
done.
/
/
if
(GetEa
-
>NextEntryOffset
=
=
0
) {
break
;
}
/
/
/
/
Remember this as the previous ea value. Also update the
buffer
/
/
length values
and
the
buffer
offset values.
/
/
LastFullEa
=
NextFullEa;
UserBufferLength
-
=
(RawEaSize
+
PrevEaPadding);
/
/
/
/
Now remember the padding bytes needed
for
this call.
/
/
PrevEaPadding
=
LongAlign(RawEaSize)
-
RawEaSize;
}
/
/
/
/
If the Ea information won
't fit in the user'
s
buffer
, then
return
/
/
an overflow status.
/
/
if
(Overflow) {
Iosb.Information
=
0
;
Iosb.Status
=
STATUS_BUFFER_OVERFLOW;
/
/
/
/
Otherwise
return
the length of the data returned.
/
/
}
else
{
/
/
/
/
Return the length of the
buffer
filled
and
a success
/
/
status.
/
/
Iosb.Information
=
Offset;
Iosb.Status
=
STATUS_SUCCESS;
}
DebugTrace(
0
, Dbg, (
"Status -> %08lx\n"
, Iosb.Status));
DebugTrace(
0
, Dbg, (
"Information -> %08lx\n"
, Iosb.Information));
DebugTrace(
-
1
, Dbg, (
"NtfsQueryEaUserEaList: Exit\n"
));
return
Iosb;
}
_QWORD
*
__fastcall NtfsQueryEaUserEaList(_QWORD
*
a1, _FILE_FULL_EA_INFORMATION
*
ea_blocks_for_file, __int64 out_buf, __int64 a4, unsigned
int
out_buf_length, _FILE_GET_EA_INFORMATION
*
eaList, char a7)
{
int
v8;
/
/
edi
unsigned
int
v9;
/
/
ebx
unsigned
int
padding;
/
/
er15
_FILE_GET_EA_INFORMATION
*
GetEa;
/
/
r12
ULONG v12;
/
/
er14
unsigned __int8 v13;
/
/
r13
_FILE_GET_EA_INFORMATION
*
curEaList;
/
/
rbx
unsigned
int
v15;
/
/
ebx
_DWORD
*
v16;
/
/
r13
unsigned
int
ea_block_size;
/
/
er14
unsigned
int
v18;
/
/
ebx
_FILE_FULL_EA_INFORMATION
*
ea_block;
/
/
rdx
char v21;
/
/
al
ULONG v22;
/
/
[rsp
+
20h
] [rbp
-
38h
]
unsigned
int
v23;
/
/
[rsp
+
24h
] [rbp
-
34h
] BYREF
_DWORD
*
v24;
/
/
[rsp
+
28h
] [rbp
-
30h
]
struct _STRING DestinationString;
/
/
[rsp
+
30h
] [rbp
-
28h
] BYREF
STRING SourceString;
/
/
[rsp
+
40h
] [rbp
-
18h
] BYREF
unsigned
int
offest;
/
/
[rsp
+
A0h] [rbp
+
48h
]
v8
=
0
;
*
a1
=
0i64
;
v24
=
0i64
;
v9
=
0
;
offest
=
0
;
padding
=
0
;
a1[
1
]
=
0i64
;
while
(
1
)
{
/
/
索引ealist中的成员,用作下面的查找。
GetEa
=
(_FILE_GET_EA_INFORMATION
*
)((char
*
)eaList
+
v9);
*
(_QWORD
*
)&DestinationString.Length
=
0i64
;
DestinationString.
Buffer
=
0i64
;
*
(_QWORD
*
)&SourceString.Length
=
0i64
;
SourceString.
Buffer
=
0i64
;
*
(_QWORD
*
)&DestinationString.Length
=
GetEa
-
>EaNameLength;
DestinationString.MaximumLength
=
DestinationString.Length;
DestinationString.
Buffer
=
GetEa
-
>EaName;
RtlUpperString(&DestinationString, &DestinationString);
if
( !(unsigned __int8)NtfsIsEaNameValid(&DestinationString) )
/
/
检查ealist中成员的name是否有效
break
;
v12
=
GetEa
-
>NextEntryOffset;
v13
=
GetEa
-
>EaNameLength;
v22
=
GetEa
-
>NextEntryOffset
+
v9;
for
( curEaList
=
eaList; ; curEaList
=
(_FILE_GET_EA_INFORMATION
*
)((char
*
)curEaList
+
curEaList
-
>NextEntryOffset) )
/
/
遍历查询的EaList
{
if
( curEaList
=
=
GetEa )
{
v15
=
offest;
v16
=
(_DWORD
*
)(a4
+
padding
+
offest);
if
( (unsigned __int8)NtfsLocateEaByName(
/
/
根据name查找对应的Ea信息
ea_blocks_for_file,
*
(unsigned
int
*
)(out_buf
+
4
),
&DestinationString,
&v23) )
{
ea_block
=
(_FILE_FULL_EA_INFORMATION
*
)((char
*
)ea_blocks_for_file
+
v23);
ea_block_size
=
ea_block
-
>EaValueLength
+
ea_block
-
>EaNameLength
+
9
;
/
/
计算内存拷贝大小
if
( ea_block_size <
=
out_buf_length
-
padding )
/
/
防溢出检查
/
/
两个uint32相减以后发生整数溢出绕过检查
{
memmove(v16, ea_block, ea_block_size);
/
/
溢出点
*
v16
=
0
;
goto LABEL_8;
}
}
else
{
ea_block_size
=
GetEa
-
>EaNameLength
+
9
;
/
/
9
=
4
(
next
entry offset)
+
1
(flags)
+
1
(name length)
+
2
(value length)
+
1
(null byte)
if
( ea_block_size
+
padding <
=
out_buf_length )
{
*
v16
=
0
;
*
((_BYTE
*
)v16
+
4
)
=
0
;
*
((_BYTE
*
)v16
+
5
)
=
GetEa
-
>EaNameLength;
*
((_WORD
*
)v16
+
3
)
=
0
;
memmove(v16
+
2
, GetEa
-
>EaName, GetEa
-
>EaNameLength);
SourceString.Length
=
DestinationString.Length;
SourceString.MaximumLength
=
DestinationString.Length;
SourceString.
Buffer
=
(PCHAR)(v16
+
2
);
RtlUpperString(&SourceString, &SourceString);
v15
=
offest;
*
((_BYTE
*
)v16
+
GetEa
-
>EaNameLength
+
8
)
=
0
;
LABEL_8:
v18
=
ea_block_size
+
padding
+
v15;
offest
=
v18;
if
( !a7 )
{
if
( v24 )
*
v24
=
(_DWORD)v16
-
(_DWORD)v24;
if
( GetEa
-
>NextEntryOffset )
/
/
判断是ealist中是否还有其他成员
{
v24
=
v16;
out_buf_length
-
=
ea_block_size
+
padding;
/
/
总长度减去已经拷贝的长度
padding
=
((ea_block_size
+
3
) &
0xFFFFFFFC
)
-
ea_block_size;
/
/
padding的计算
goto LABEL_26;
}
}
LABEL_12:
a1[
1
]
=
v18;
LABEL_13:
*
(_DWORD
*
)a1
=
v8;
return
a1;
}
}
v21
=
NtfsStatusDebugFlags;
a1[
1
]
=
0i64
;
if
( v21 )
NtfsStatusTraceAndDebugInternal(
0i64
,
2147483653i64
,
919406i64
);
v8
=
-
2147483643
;
goto LABEL_13;
}
if
( v13
=
=
curEaList
-
>EaNameLength && !memcmp(GetEa
-
>EaName, curEaList
-
>EaName, v13) )
break
;
}
if
( !v12 )
{
v18
=
offest;
goto LABEL_12;
}
LABEL_26:
v9
=
v22;
}
a1[
1
]
=
v9;
if
( NtfsStatusDebugFlags )
NtfsStatusTraceAndDebugInternal(
0i64
,
2147483667i64
,
919230i64
);
*
(_DWORD
*
)a1
=
-
2147483629
;
return
a1;
}
_QWORD
*
__fastcall NtfsQueryEaUserEaList(_QWORD
*
a1, _FILE_FULL_EA_INFORMATION
*
ea_blocks_for_file, __int64 out_buf, __int64 a4, unsigned
int
out_buf_length, _FILE_GET_EA_INFORMATION
*
eaList, char a7)
{
int
v8;
/
/
edi
unsigned
int
v9;
/
/
ebx
unsigned
int
padding;
/
/
er15
_FILE_GET_EA_INFORMATION
*
GetEa;
/
/
r12
ULONG v12;
/
/
er14
unsigned __int8 v13;
/
/
r13
_FILE_GET_EA_INFORMATION
*
curEaList;
/
/
rbx
unsigned
int
v15;
/
/
ebx
_DWORD
*
v16;
/
/
r13
unsigned
int
ea_block_size;
/
/
er14
unsigned
int
v18;
/
/
ebx
_FILE_FULL_EA_INFORMATION
*
ea_block;
/
/
rdx
char v21;
/
/
al
ULONG v22;
/
/
[rsp
+
20h
] [rbp
-
38h
]
unsigned
int
v23;
/
/
[rsp
+
24h
] [rbp
-
34h
] BYREF
_DWORD
*
v24;
/
/
[rsp
+
28h
] [rbp
-
30h
]
struct _STRING DestinationString;
/
/
[rsp
+
30h
] [rbp
-
28h
] BYREF
STRING SourceString;
/
/
[rsp
+
40h
] [rbp
-
18h
] BYREF
unsigned
int
offest;
/
/
[rsp
+
A0h] [rbp
+
48h
]
v8
=
0
;
*
a1
=
0i64
;
v24
=
0i64
;
v9
=
0
;
offest
=
0
;
padding
=
0
;
a1[
1
]
=
0i64
;
while
(
1
)
{
/
/
索引ealist中的成员,用作下面的查找。
GetEa
=
(_FILE_GET_EA_INFORMATION
*
)((char
*
)eaList
+
v9);
*
(_QWORD
*
)&DestinationString.Length
=
0i64
;
DestinationString.
Buffer
=
0i64
;
*
(_QWORD
*
)&SourceString.Length
=
0i64
;
SourceString.
Buffer
=
0i64
;
*
(_QWORD
*
)&DestinationString.Length
=
GetEa
-
>EaNameLength;
DestinationString.MaximumLength
=
DestinationString.Length;
DestinationString.
Buffer
=
GetEa
-
>EaName;
RtlUpperString(&DestinationString, &DestinationString);
if
( !(unsigned __int8)NtfsIsEaNameValid(&DestinationString) )
/
/
检查ealist中成员的name是否有效
break
;
v12
=
GetEa
-
>NextEntryOffset;
v13
=
GetEa
-
>EaNameLength;
v22
=
GetEa
-
>NextEntryOffset
+
v9;
for
( curEaList
=
eaList; ; curEaList
=
(_FILE_GET_EA_INFORMATION
*
)((char
*
)curEaList
+
curEaList
-
>NextEntryOffset) )
/
/
遍历查询的EaList
{
if
( curEaList
=
=
GetEa )
{
v15
=
offest;
v16
=
(_DWORD
*
)(a4
+
padding
+
offest);
if
( (unsigned __int8)NtfsLocateEaByName(
/
/
根据name查找对应的Ea信息
ea_blocks_for_file,
*
(unsigned
int
*
)(out_buf
+
4
),
&DestinationString,
&v23) )
{
ea_block
=
(_FILE_FULL_EA_INFORMATION
*
)((char
*
)ea_blocks_for_file
+
v23);
ea_block_size
=
ea_block
-
>EaValueLength
+
ea_block
-
>EaNameLength
+
9
;
/
/
计算内存拷贝大小
if
( ea_block_size <
=
out_buf_length
-
padding )
/
/
防溢出检查
/
/
两个uint32相减以后发生整数溢出绕过检查
{
memmove(v16, ea_block, ea_block_size);
/
/
溢出点
*
v16
=
0
;
goto LABEL_8;
}
}
else
{
ea_block_size
=
GetEa
-
>EaNameLength
+
9
;
/
/
9
=
4
(
next
entry offset)
+
1
(flags)
+
1
(name length)
+
2
(value length)
+
1
(null byte)
if
( ea_block_size
+
padding <
=
out_buf_length )
{
*
v16
=
0
;
*
((_BYTE
*
)v16
+
4
)
=
0
;
*
((_BYTE
*
)v16
+
5
)
=
GetEa
-
>EaNameLength;
*
((_WORD
*
)v16
+
3
)
=
0
;
memmove(v16
+
2
, GetEa
-
>EaName, GetEa
-
>EaNameLength);
SourceString.Length
=
DestinationString.Length;
SourceString.MaximumLength
=
DestinationString.Length;
SourceString.
Buffer
=
(PCHAR)(v16
+
2
);
RtlUpperString(&SourceString, &SourceString);
v15
=
offest;
*
((_BYTE
*
)v16
+
GetEa
-
>EaNameLength
+
8
)
=
0
;
LABEL_8:
v18
=
ea_block_size
+
padding
+
v15;
offest
=
v18;
if
( !a7 )
{
if
( v24 )
*
v24
=
(_DWORD)v16
-
(_DWORD)v24;
if
( GetEa
-
>NextEntryOffset )
/
/
判断是ealist中是否还有其他成员
{
v24
=
v16;
out_buf_length
-
=
ea_block_size
+
padding;
/
/
总长度减去已经拷贝的长度
padding
=
((ea_block_size
+
3
) &
0xFFFFFFFC
)
-
ea_block_size;
/
/
padding的计算
goto LABEL_26;
}
}
LABEL_12:
a1[
1
]
=
v18;
LABEL_13:
*
(_DWORD
*
)a1
=
v8;
return
a1;
}
}
v21
=
NtfsStatusDebugFlags;
a1[
1
]
=
0i64
;
if
( v21 )
NtfsStatusTraceAndDebugInternal(
0i64
,
2147483653i64
,
919406i64
);
v8
=
-
2147483643
;
goto LABEL_13;
}
if
( v13
=
=
curEaList
-
>EaNameLength && !memcmp(GetEa
-
>EaName, curEaList
-
>EaName, v13) )
break
;
}
if
( !v12 )
{
v18
=
offest;
goto LABEL_12;
}
LABEL_26:
v9
=
v22;
}
a1[
1
]
=
v9;
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课