首页
社区
课程
招聘
[原创]Windows NTFS本地提权漏洞 CVE-2021-31956
发表于: 2023-6-28 18:05 11973

[原创]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;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2023-7-5 15:07 被hml189编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (1)
雪    币: 3573
活跃值: (31026)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark
2023-6-29 09:35
0
游客
登录 | 注册 方可回帖
返回
//