CheatNOguilty 发表于 2025-1-7 11:55:34

ETW hook原理与C代码实现,附完整源代码

1.什么是ETWHOOK?
简单的说就是在有PatchGurad的windows系统上SSDT hook的一种替代方案



2.ETWHOOK实现步骤
(1).替换HalpPrivatDispatchTable(可以通过MmGetSystemRoutineAddress函数获取)表中的的EtwpReserveWithPmcCounters函数指针为自己的代理函数,代理函数的功能是在堆栈中寻找系统调用的目标内核函数指针并且在找到的时候替换它;
(2).开启Windows的ETW功能,并且经过一系列的设置使得系统调用时能进入EtwpReserveWithPmcCounters函数(经过第一步的操作之后EtwpReserveWithPmcCounters已经被替换成自己的代理函数了);


3.代码
(1).HalColleePmcCounts代理函数的实现:
VOID ProxyEtwpReserveWithPmcCounters(PVOID Context, ULONGLONG TraceBuff)
{


        ULONG Signate = SYSCALL_SIGNATE;
        ULONG Magic = SYSCALL_MAGIC;
        //PULONG64 RspPos = _AddressOfReturnAddress();
        //PULONG64 RspLimit = __readgsqword(0x1a8);
        LARGE_INTEGER* RspPos = _AddressOfReturnAddress();
        LARGE_INTEGER* RspLimit = __readgsqword(0x1a8);
        ULONG64 KiSystemServiceRepeat = HookInfo->KiSystemServiceRepeat;
       
        if (KeGetCurrentIrql() <= DISPATCH_LEVEL)
        {
                if (ExGetPreviousMode() == KernelMode)return;
                if (!KiSystemServiceRepeat)
                {
                        DbgPrint("failed to find KiSystemServiceRepeat\r\n");
                        return;
                }
                while (RspPos <= RspLimit)
                {
                        //if (*((PUSHORT)(RspPos)) == Magic)
                        if((RspPos->LowPart&0x0000ffff)== Signate)
                        {
                               
                                if (RspPos.LowPart == Magic)
                                {
                                               
                                        for (; (ULONG64)RspPos <= (ULONG64)RspLimit; ++RspPos)
                                        {
                                               
                                                if (RspPos->QuadPart >= (KiSystemServiceRepeat & 0XFFFFFFFFFFFFF000) &&
                                                        RspPos->QuadPart <= ((KiSystemServiceRepeat & 0XFFFFFFFFFFFFF000) + 0x2000))
                                                {
                                                       
                                                        //TargetFind!
                                                       
                                                        if (RspPos.QuadPart == HookInfo->OrgFunc)
                                                        {
                                                                HookInfo->Lock = FALSE;
                                                                KiSystemServiceRepeat = HookInfo->HookFunc;
                                                                InterlockedExchange64(&(RspPos.QuadPart), KiSystemServiceRepeat);
                                                        }
                                                        goto sub_1;
                                                }
                                        }
                                }
                               
                        }
                        ++RspPos;
                       
                }

        }
sub_1:

        return HookInfo->OrigeHalCollectPmcCounters(Context, TraceBuff);
}(2).最麻烦的是开启ETW功能以及开启ETW子功能PMC计数
//开启ETW子功能PMC计数需要获取MaxPmcCounter
UCHAR* GetEtwpMaxPmcCounter()
{
        UCHAR* Ret = 0;
        LONG64 KernelBase = 0;
        UNICODE_STRING KernelModuleName;
        UCHAR FeaturesCode[] = { 0x44,0x3b,0x05,'*','*','*','*',0x0f,0x87,'*','*','*','*',0x83,0xb9,'*','*','*','*',0x01,0x0f,0x84,'*','*','*','*',0x48,0x83,0xb9,'*','*','*','*',0x00,0x75,'*' };
        UCHAR SectionNames[] = { 'P','A','G','E',0,0,0,0 };
        RtlInitUnicodeString(&KernelModuleName, L"ntoskrnl.exe");
        KernelBase = HookInfo->OsKernelLoadBase;
        if (!KernelBase)
        {
                DbgPrint("get kernel module base fail\n");
                return 0;
        }
        IMAGE_FILE_HEADER* FileHeader = KernelBase + ((IMAGE_DOS_HEADER*)KernelBase)->e_lfanew + 4;
        USHORT SectionCount = FileHeader->NumberOfSections;
        IMAGE_OPTIONAL_HEADER64* OptionalHeader = FileHeader + 1;
        IMAGE_SECTION_HEADER* SectionHeader = OptionalHeader + 1;
        LONG64 StratAdd = 0;
        LONG64 EndAdd = 0;
        for (int i = 0; i < SectionCount; ++i)
        {
               
                if (RtlCompareMemory(&(SectionHeader.Name), SectionNames, 8) == 8)
                {
                        StratAdd = SectionHeader.VirtualAddress + KernelBase;
                        EndAdd = ((LONG64)(SectionHeader.Misc.VirtualSize & 0xfffff000)) + 0x1000 + StratAdd;
                       
                        if (SearchFunctionOfCharacteristicCode(FeaturesCode,
                                36,
                                StratAdd,
                                EndAdd,
                                &Ret) == STATUS_SUCCESS)
                        {
                               
                                return (*((PULONG)(Ret+3)))+((LONG64)Ret+7);
                        }
                        break;
                }
        }
        return 0;

}


//开启ETW功能分支功能PerformatsCounts计数功能
NTSTATUS SetPmcCounts()
{
        NTSTATUS state = STATUS_UNSUCCESSFUL;
        if (!HookInfo->EtwOffNo)
        {
                return state;
        }
        PETW_PMC_INFO PmcInfo = ExAllocatePool(PagedPool, 0X1000);
        if (!PmcInfo)
        {
                DbgPrint("Allocate PmcInfo fail!\n");
                return state;
        }
       
        PmcInfo->EventTraceInformationClass = EventTraceProfileCounterListInformation;
        PmcInfo->TraceHandle = 2;
        PmcInfo->ProfileSource = 1;
        UCHAR* EtwPmcMaxCount = GetEtwpMaxPmcCounter();
        UCHAR* OrgMaxCount = 0;
        if (EtwPmcMaxCount)
        {
                OrgMaxCount = *EtwPmcMaxCount;
                if (OrgMaxCount <= 1)*EtwPmcMaxCount = 2;
        }
        state = ZwSetSystemInformation(SystemPerformanceTraceInformation, PmcInfo, sizeof(ETW_PMC_INFO));
        if (!NT_SUCCESS(state))
        {
                DbgPrint("pmc open fail\n");
                return state;
        }
        if (EtwPmcMaxCount)
        {
                if (OrgMaxCount <= 1)*EtwPmcMaxCount = OrgMaxCount;
        }
        PETW_SYSTEM_TRACE SysEventTraceInfo = ExAllocatePool(PagedPool, 0x1000);
        if (!SysEventTraceInfo)
        {
                DbgPrint("allocate System Event Trace info fail\r\n");
                return STATUS_UNSUCCESSFUL;
        }
        SysEventTraceInfo->EventTraceInformationClass = EventTraceProfileEventListInformation;
        SysEventTraceInfo->TraceHandle = 2;
        //SysEventTraceInfo->HookId = HookInfo->SyscallHookId;
        SysEventTraceInfo->HookId = SYSCALL_SIGNATE;

        state = ZwSetSystemInformation(SystemPerformanceTraceInformation, SysEventTraceInfo, sizeof(ETW_SYSTEM_TRACE));
        if (!NT_SUCCESS(state))
        {
                DbgPrint("failed to configure pmc event,errcode=%x\r\n", state);
                return state;
        }
        if (SysEventTraceInfo)ExFreePool(SysEventTraceInfo);
        if (PmcInfo)ExFreePool(PmcInfo);
        return state;
}

//关闭ETW事件跟踪功能
NTSTATUS EndEventTraceWork()
{

        NTSTATUS state = STATUS_UNSUCCESSFUL;
        ULONG LenthRetVaul=0;
        EVENT_TRACE_PROPERTIES* EtwInfo = ExAllocatePool(PagedPool, 0x1000);
        if (!EtwInfo)
        {
                DbgPrint("2_AllocateMemFial!\n");
        }
        memset(EtwInfo, 0, 0x1000);
        EtwInfo->Wnode.BufferSize = 0x1000;
        EtwInfo->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
        RtlInitUnicodeString(&(EtwInfo->ProviderName), L"Circular Kernel Context Logger");
        EtwInfo->Wnode.Guid = CkclSessionGuid;
        EtwInfo->Wnode.ClientContext = 1;
        EtwInfo->BufferSize = sizeof(ULONG);
        EtwInfo->MinimumBuffers = 2;
        EtwInfo->MaximumBuffers = 2;
        EtwInfo->LogFileMode = EVENT_TRACE_BUFFERING_MODE;

        state = ZwTraceControl(EtwpStopTrace, EtwInfo, 0x1000, EtwInfo, 0x1000, &LenthRetVaul);
        if (!NT_SUCCESS(state))
        {
                DbgPrint("stop event trace fail!-state=%x\n", state);
        }
        if (EtwInfo)ExFreePool(EtwInfo);
        HookInfo->EtwOffNo = FALSE;
        return state;

}

//开启ETW事件跟踪功能
NTSTATUS StartEventTraceWork()
{
        NTSTATUS state=STATUS_UNSUCCESSFUL;
        ULONG LenthRetVaul=0;
        EVENT_TRACE_PROPERTIES* EtwInfo = ExAllocatePool(PagedPool, 0x1000);
        if (!EtwInfo)
        {
                DbgPrint("1_AllocateMemFial!\n");
        }
        memset(EtwInfo, 0, 0x1000);
       
        EtwInfo->Wnode.BufferSize = 0x1000;
        EtwInfo->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
        RtlInitUnicodeString(&(EtwInfo->ProviderName), L"Circular Kernel Context Logger");
        EtwInfo->Wnode.Guid = CkclSessionGuid;
        EtwInfo->Wnode.ClientContext = 1;
        EtwInfo->BufferSize = sizeof(ULONG);
        EtwInfo->MinimumBuffers = 2;
        EtwInfo->MaximumBuffers = 2;
        EtwInfo->LogFileMode = EVENT_TRACE_BUFFERING_MODE;
        state = ZwTraceControl(EtwpStartTrace, EtwInfo, 0X1000, EtwInfo,0x1000, &LenthRetVaul);
        if (!NT_SUCCESS(state) && state != STATUS_OBJECT_NAME_COLLISION)
        {
                DbgPrint("EventTrace Start Fail-state=%x\n",state);
        }
        EtwInfo->EnableFlags = EVENT_TRACE_FLAG_SYSTEMCALL;

        state = ZwTraceControl(EtwpUpdateTrace, EtwInfo, 0x1000, EtwInfo, 0x1000, &LenthRetVaul);
        if (!NT_SUCCESS(state))
        {
                DbgPrint("failed to enable syscall etw-state=%x\n", state);
        }
        if (EtwInfo)
        {
                ExFreePool(EtwInfo);
        }
        return state;
}
4.具体原理
WINDOWS的.data节一般是存放可以变的全局变量的,windows不可避免的要使用全局变量存放一些比较底层函数指针,所谓的偷指针就是修改.data节区的函数指针。而在这里,最好玩的是HalPrivateDispatchTable,这个是windows 的ntoskenl.exe为了方便使用HAL的导出函数,把他们存放在统一的地方。而HAL,用到的地方肯定很多,ETW 正是如此。
看一下系统调用ETW的调用路径。
这里其实可以看到,call rax 其实就是正常的系统调用,而再进入ETW系统调用之前,他把原始的系统调用存放在了栈上,这就导致我们拦截到ETW的时候,可以修改栈上的位置,来进行HOOK Syscall。如果说之前无法定位,现在可以通过这个栈上面的magic number来定位系统调用的目标函数的地址,从而替换了。
继续跟到EtwTraceSiloKernelEvent里面,可以发现,无论参数怎么样,这个函数调用了EtwpLogKernelEvent。继续跟踪EtwpLogKernelEvent会发现EtwpLogKernelEvent会调用EtwpReserveWithPmcCounters。而EtwpReserveWithPmcCounters就是这次事件的主角,关键部分代码为:图中红圈中的地方就是EtwpReserveWithPmcCounters通过CFG保护的方式调用HalPrivateDispatchTable表中的HalCollectPmcCounters。我们替换HalPrivateDispatchTable表中的EtwpReserveWithPmcCounters函数指针为我们自己的函数,在我们自己的函数里就可以在栈上查找并替换系统调用的目标函数从而达到HOOk的目的。最后附上完整源代码**** Hidden Message *****





admin 发表于 2025-1-7 17:25:28

不错

Black 发表于 2025-1-8 15:08:15

感谢分享,我会认真学习的!

ldljlzw 发表于 2025-1-9 06:59:52

感谢分享,我会认真学习的!

f17629 发表于 2025-3-3 17:01:19

ETWHOOK会不会引起蓝屏
页: [1]
查看完整版本: ETW hook原理与C代码实现,附完整源代码