基本是参照这个写的:etw_HOOK
这个是用C++写的,对C++不是很精通的人可能看着很难读懂,所以我用纯C代码复刻了一下。
1.什么是ETWHOOK?
简单的说就是在有PatchGurad的windows系统上SSDT hook的一种替代方案
2.ETWHOOK实现步骤
(1).替换HalpPrivatDispatchTable(可以通过MmGetSystemRoutineAddress函数获取)表中的的HalColleePmcCounts函数指针为自己的代理函数,代理函数的功能是在堆栈中寻找系统调用的目标内核函数指针并且在找到的时候替换它;
(2).开启Windows的ETW功能,并且经过一系列的设置使得系统调用时能进入HalColleePmcCounts函数(经过第一步的操作之后HalColleePmcCounts已经被替换成自己的代理函数了);
3.代码
(1).HalColleePmcCounts代理函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | VOID ProxyHalPrivateDispatchTable(PVOID Context, ULONGLONG TraceBuff){ USHORT Magic = 0xf33;\\系统调用在栈上的关键字1 ULONG Signate = 0x501802;\\系统调用在栈上的关键字2 PULONG RspPos = _AddressOfReturnAddress(); PULONG 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[2] == Signate) { for (; (ULONG64)RspPos <= (ULONG64)RspLimit; ++RspPos) { if ((*((PULONG64)(RspPos)) >= (KiSystemServiceRepeat & 0XFFFFFFFFFFFFF000)) && (*((PULONG64)(RspPos)) <= ((KiSystemServiceRepeat & 0XFFFFFFFFFFFFF000) + 0x2000))) { //TargetFind! if (((PULONG64)RspPos)[9] == HookInfo->OrgFunc) { HookInfo->Lock = FALSE; _disable(); ((PULONG64)RspPos)[9] = HookInfo->HookFunc; _enable(); } goto sub_1; } } } } ++RspPos; } }sub_1: return HookInfo->OrigeHalCollectPmcCounters(Context, TraceBuff);} |
(2).最麻烦的是开启ETW功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | 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;}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;} |
(3).开启ETW功能分支功能PerformatsCounts计数功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | 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[i].Name), SectionNames, 8) == 8) { StratAdd = SectionHeader[i].VirtualAddress + KernelBase; EndAdd = ((LONG64)(SectionHeader[i].Misc.VirtualSize & 0xfffff000)) + 0x1000 + StratAdd; if (SearchFunctionOfCharacteristicCode(FeaturesCode, 36, StratAdd, EndAdd, &Ret) == STATUS_SUCCESS) { return (*((PULONG)(Ret+3)))+((LONG64)Ret+7); } break; } } return 0;}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[0] = 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[0] = HookInfo->SyscallHookId; 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;} |
4.具体原理
WINDOWS的.data节一般是存放可以变的全局变量的,所谓的偷指针就是修改.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表中的HalCollectPmcCounters函数指针为我们自己的函数,在我们自己的函数里就可以在栈上查找并替换系统调用的目标函数从而达到HOOk的目的。
最后一点疑问:PG只要监控HalPrivateDispatchTable表就能修复这个漏洞,但貌似知道现在这个漏洞仍然可用。