-
-
[原创]Windows NTFS本地提权漏洞 CVE-2021-31956
-
发表于: 2023-6-28 18:05 11973
-
参考链接:
https://research.nccgroup.com/2021/07/15/cve-2021-31956-exploiting-the-windows-kernel-ntfs-with-wnf-part-1/
https://research.nccgroup.com/2021/08/17/cve-2021-31956-exploiting-the-windows-kernel-ntfs-with-wnf-part-2/
https://paper.seebug.org/1743
使用PipeAttribution构造任意地址读后,修改_WNF_NAME_INSTANCE结构体内的指针_WNF_STATE_DATA实现任意地址写
https://dawnslab.jd.com/CVE-2021-31956/
https://github.com/hzshang/CVE-2021-31956
使用使用NtQueryWnfStateData和NtUpDateWnfStateData API来造成任意地址的读写(需要构造AllocateSize和DataSize成员)
https://bbs.kanxue.com/thread-271140.htm
https://github.com/aazhuliang/CVE-2021-31956-EXP
漏洞在windows的NTFS文件系统驱动上(C:\Windows\System32\drivers\ntfs.sys)的NtfsQueryEaUserEaList函数中
NTFS文件系统允许为每一个文件额外存储若干个键值对属性,称之为EA(Extend Attribution) 。可以通过ZwSetEaFile为文件创建EA,ZwQueryEaFile查询文件EA
泄露的NT5.1中有NtfsQueryEaUserEaList的源码
https://github.com/0x5bfa/NT5.1/blob/master/Source/XPSP1/NT/base/fs/ntfs/ea.c#L1461
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,具体可看以下论文
https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf
EXP:https://github.com/aazhuliang/CVE-2021-31956-EXP
仿照EXP改的可以触发溢出的POC
可以看到整数下溢
Windows10引入了新的方式进行堆块管理,称为Segment Heap
详见:https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf
中文翻译:https://paper.seebug.org/1743
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 利用相关文章
https://docplayer.net/145030841-The-windows-notification-facility.html
https://blog.quarkslab.com/playing-with-the-windows-notification-facility-wnf.html
结构体_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实现任意地址读
PipeAttribute详见:
https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf
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 地址。
https://github.com/freeide/CVE-2021-31955-POC
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;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!