其他
剖析InfinityHook原理 掀起一场更激烈的攻与防恶战
什么是 InfinityHook
什么是 Event Tracing for Windows
控制器(Controllers):用于启动和停止事件跟踪会话并启用提供程序。
提供者(Providers):用于提供事件。
消费者(Consumers):用于消费事件。
控制器(Controllers)
提供者(Providers)
消费者(Consumers)
InfinityHook 原理剖析
BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGFILE_PATH) + sizeof(KERNEL_LOGGER_NAME);
pSessionProperties = (EVENT_TRACE_PROPERTIES*) malloc(BufferSize);
ZeroMemory(pSessionProperties, BufferSize);
Wnode.BufferSize = BufferSize;
Wnode.Flags = WNODE_FLAG_TRACED_GUID;
Wnode.ClientContext = 1; //QPC clock resolution
Wnode.Guid = SystemTraceControlGuid;
EnableFlags = EVENT_TRACE_FLAG_NETWORK_TCPIP;
LogFileMode = EVENT_TRACE_FILE_MODE_CIRCULAR;
MaximumFileSize = 5; // 5 MB
LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME);
StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH);
status = StartTrace((PTRACEHANDLE)&SessionHandle, KERNEL_LOGGER_NAME, pSessionProperties);
WNODE_HEADER Wnode;
//
// data provided by caller
ULONG BufferSize; // buffer size for logging (kbytes)
ULONG MinimumBuffers; // minimum to preallocate
ULONG MaximumBuffers; // maximum buffers allowed
ULONG MaximumFileSize; // maximum logfile size (in MBytes)
ULONG LogFileMode; // sequential, circular
ULONG FlushTimer; // buffer flush timer, in seconds
ULONG EnableFlags; // trace enable flags
union {
LONG AgeLimit; // unused
LONG FlushThreshold; // Number of buffers to fill before flushing
} DUMMYUNIONNAME;
// data returned to caller
ULONG NumberOfBuffers; // no of buffers in use
ULONG FreeBuffers; // no of buffers free
ULONG EventsLost; // event records lost
ULONG BuffersWritten; // no of buffers written to file
ULONG LogBuffersLost; // no of logfile write failures
ULONG RealTimeBuffersLost; // no of rt delivery failures
HANDLE LoggerThreadId; // thread id of Logger
ULONG LogFileNameOffset; // Offset to LogFileName
ULONG LoggerNameOffset; // Offset to LoggerName
} EVENT_TRACE_PROPERTIES, *PEVENT_TRACE_PROPERTIES;
typedef struct _WNODE_HEADER
{
ULONG BufferSize; // Size of entire buffer inclusive of this ULONG
ULONG ProviderId; // Provider Id of driver returning this buffer
union
{
ULONG64 HistoricalContext; // Logger use
struct
{
ULONG Version; // Reserved
ULONG Linkage; // Linkage field reserved for WMI
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
union
{
ULONG CountLost; // Reserved
HANDLE KernelHandle; // Kernel handle for data block
LARGE_INTEGER TimeStamp; // Timestamp as returned in units of 100ns
// since 1/1/1601
} DUMMYUNIONNAME2;
GUID Guid; // Guid for data block returned with results
ULONG ClientContext;
ULONG Flags; // Flags, see below
} WNODE_HEADER, *PWNODE_HEADER;
ULONG
WMIAPI
StartTraceA (
_Out_ PTRACEHANDLE TraceHandle,
_In_ LPCSTR InstanceName,
_Inout_ PEVENT_TRACE_PROPERTIES Properties
);
pSessionProperties = (EVENT_TRACE_PROPERTIES*) malloc(BufferSize);
StartTrace 的内部实现
您可以实时或从日志文件中使用事件,并使用它们来调试应用程序或确定应用程序中发生性能问题的位置。
InfinityHook 的实现
{
CHAR Padding[24];
UNICODE_STRING InstanceName;
};
if (pProperty == NULL) {
KeBugCheckEx(HAL_MEMORY_ALLOCATION, PAGE_SIZE, 0, NULL, 0);
return STATUS_MEMORY_NOT_ALLOCATED;
}
RtlZeroMemory(pProperty, PAGE_SIZE);
RtlInitUnicodeString(&InstanceName, L"Circular Kernel Context Logger");
Query performance counter (QPC). QPC计数器提供高分辨率时间戳,不受系统时钟调整的影响。存储在事件中的时间戳等同于QueryPerformanceCounter API返回的值。
System time(系统时间)。
CPU cycle counter(CPU循环计数器). CPU计数器提供最高分辨率的时间戳,并且是检索资源最少的。但是,CPU计数器不可靠,不应在生产中使用。例如,在某些计算机上,除了在某些状态下停止之外,定时器还会因热量和功率变化而改变频率。
const GUID CkclSessionGuid = { 0x54dea73a, 0xed1f, 0x42a4, { 0xaf, 0x71, 0x3e, 0x63, 0xd0, 0x56, 0xf1, 0x74 } };
Wnode.ClientContext = 3;
Wnode.Flags = WNODE_FLAG_TRACED_GUID;
Wnode.Guid = CkclSessionGuid;
BufferSize = sizeof(ULONG);
LogFileMode = EVENT_TRACE_BUFFERING_MODE;
MinimumBuffers = pProperty->MaximumBuffers = 2;
InstanceName = InstanceName;
NTSYSCALLAPI
NTSTATUS
NTAPI
NtTraceControl(
_In_ ULONG FunctionCode,
_In_reads_bytes_opt_(InBufferLen) PVOID InBuffer,
_In_ ULONG InBufferLen,
_Out_writes_bytes_opt_(OutBufferLen) PVOID OutBuffer,
_In_ ULONG OutBufferLen,
_Out_ PULONG ReturnLength
);
NtTraceControl(EtwpStartTrace, pProperty, PAGE_SIZE, pProperty, PAGE_SIZE, &ReturnLength);
// 停止
NtTraceControl(EtwpStopTrace, pProperty, PAGE_SIZE, pProperty, PAGE_SIZE, &ReturnLength);
// 更新
NtTraceControl(EtwpUpdateTrace, pProperty, PAGE_SIZE, pProperty, PAGE_SIZE, &ReturnLength);
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
ULONG ReturnLength = 0;
CKCL_TRACE_PROPERTIES *pProperty = (CKCL_TRACE_PROPERTIES *)ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, ALLOC_TAG);
if (pProperty == NULL) {
KeBugCheckEx(HAL_MEMORY_ALLOCATION, PAGE_SIZE, 0, NULL, 0);
return STATUS_MEMORY_NOT_ALLOCATED;
}
RtlZeroMemory(pProperty, PAGE_SIZE);
UNICODE_STRING InstanceName;
RtlInitUnicodeString(&InstanceName, L"Circular Kernel Context Logger");
pProperty->Wnode.BufferSize = PAGE_SIZE;
pProperty->Wnode.ClientContext = 3;
pProperty->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
pProperty->Wnode.Guid = CkclSessionGuid;
pProperty->BufferSize = sizeof(ULONG);
pProperty->LogFileMode = EVENT_TRACE_BUFFERING_MODE;
pProperty->MinimumBuffers = pProperty->MaximumBuffers = 2;
pProperty->InstanceName = InstanceName;
switch (Operation)
{
case CKCL_TRACE_START:
Status = NtTraceControl(EtwpStartTrace, pProperty, PAGE_SIZE, pProperty, PAGE_SIZE, &ReturnLength);
break;
case CKCL_TRACE_END:
Status = NtTraceControl(EtwpStopTrace, pProperty, PAGE_SIZE, pProperty, PAGE_SIZE, &ReturnLength);
break;
case CKCL_TRACE_SYSCALL:
// 这里添加更多标志可以捕获更多事件
pProperty->EnableFlags = EVENT_TRACE_FLAG_SYSTEMCALL;
Status = NtTraceControl(EtwpUpdateTrace, pProperty, PAGE_SIZE, pProperty, PAGE_SIZE, &ReturnLength);
break;
default:
Status = STATUS_UNSUCCESSFUL;
break;
}
ExFreePool(pProperty);
return Status;
}
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
// 测试 CKCL 会话是否已经启动
Status = EventTraceControl(CKCL_TRACE_SYSCALL);
if (!NT_SUCCESS(Status)) {
// 没有启动 尝试打开
Status = EventTraceControl(CKCL_TRACE_START);
if (!NT_SUCCESS(Status)) {
LOG_ERROR("Start CKCL failed.", Status);
return Status;
}
Status = EventTraceControl(CKCL_TRACE_SYSCALL);
if (!NT_SUCCESS(Status)) {
LOG_ERROR("Start CKCL failed.", Status);
return Status;
}
}
LOG_INFO("CKCL is running", 0);
return Status;
}
根据相同的数值,得出EtwpDebuggerData的特征码是“?? ?? 2c 08 04 38 0c”。
不同系统上的EtwpDebuggerData可能存在于不同的区段。
PVOID KernelBase = System::GetKernelBase(&KernelSize);
if (KernelBase == NULL) {
LOG_ERROR("Get kernel base failed.", 0);
return FALSE;
}
const auto fnSearchInSection = [&](CHAR *SectionName, CHAR *Pattern, CHAR *Masks)->PVOID
{
ULONG SizeOfSection = 0;
PVOID SectionBase = Image::GetSection(KernelBase, SectionName, &SizeOfSection);
if (SectionBase == NULL) {
return NULL;
}
return Utils::FindPattern(SectionBase, SizeOfSection, Pattern, Masks);
};
PVOID EtwpDebuggerData = fnSearchInSection(".data", "\x00\x00\x2c\x08\x04\x38\x0c", "??xxxxx");
if (EtwpDebuggerData == NULL) {
EtwpDebuggerData = fnSearchInSection(".rdata", "\x00\x00\x2c\x08\x04\x38\x0c", "??xxxxx");
if (EtwpDebuggerData == NULL) {
return FALSE;
}
}
if (EtwpDebuggerDataSilo == NULL) {
LOG_ERROR("EtwpDebuggerDataSilo is bad.", EtwpDebuggerDataSilo);
return FALSE;
}
g.CkclWmiLoggerContext = EtwpDebuggerDataSilo[2];
if (g.CkclWmiLoggerContext == NULL) {
LOG_ERROR("CkclWmiLoggerContext is bad.", EtwpDebuggerDataSilo);
return FALSE;
}
通过 KVASCODE 节表是否存在,判断有没有打补丁
也可以通过 NtQuerySystemInformation 来查询判断
*/
ULONG SectionSize = 0;
PVOID SectionBase = Image::GetSection(KernelBase, "KVASCODE", &SectionSize);
if (SectionBase == NULL) {
return SyscallEntry;
}
// 判断 SyscallEntry 是否在 KVASCODE 节表内,如果不在则是真实入口直接返回
if (!(SyscallEntry >= SectionBase && SyscallEntry < (PVOID)((ULONG_PTR)SectionBase + SectionSize))) {
return SyscallEntry;
}
for (PVOID ShadowPagePtr = SyscallEntry; ; ShadowPagePtr = (PVOID)((ULONG_PTR)ShadowPagePtr + Hde.len))
{
// 解析每条汇编指令,找到第一个 jmp(e9) 出 KVASCODE 区段的指令
if (!hde64_disasm(ShadowPagePtr, &Hde)) {
break;
}
if (Hde.opcode != 0xE9) {
continue;
}
// 忽略 jmp 目标为 KVASCODE 区域内的指令
PVOID KiSystemServiceUser = (PVOID)((ULONG_PTR)ShadowPagePtr + (INT)Hde.len + (INT)Hde.imm.imm32);
if (KiSystemServiceUser >= SectionBase && KiSystemServiceUser < (PVOID)((ULONG_PTR)SectionBase + SectionSize)) {
continue;
}
// 找到 KiSystemServiceUser
SyscallEntry = KiSystemServiceUser;
break;
}
+0x000 LoggerId : Uint4B
+0x004 BufferSize : Uint4B
+0x008 MaximumEventSize : Uint4B
+0x00c LoggerMode : Uint4B
+0x010 AcceptNewEvents : Int4B
+0x014 EventMarker : [2] Uint4B
+0x01c ErrorMarker : Uint4B
+0x020 SizeMask : Uint4B
+0x028 GetCpuClock : Ptr64 int64
+0x030 LoggerThread : Ptr64 _ETHREAD
+0x038 LoggerStatus : Int4B
+0x03c FailureReason : Uint4B
+0x040 BufferQueue : _ETW_BUFFER_QUEUE
+0x050 OverflowQueue : _ETW_BUFFER_QUEUE
+0x060 GlobalList : _LIST_ENTRY
+0x070 DebugIdTrackingList : _LIST_ENTRY
+0x080 DecodeControlList : Ptr64 _ETW_DECODE_CONTROL_ENTRY
+0x088 DecodeControlCount : Uint4B
+0x090 BatchedBufferList : Ptr64 _WMI_BUFFER_HEADER
+0x090 CurrentBuffer : _EX_FAST_REF
+0x098 LoggerName : _UNICODE_STRING
+0x0a8 LogFileName : _UNICODE_STRING
+0x0b8 LogFilePattern : _UNICODE_STRING
+0x0c8 NewLogFileName : _UNICODE_STRING
+0x0d8 ClockType : Uint4B
+0x0dc LastFlushedBuffer : Uint4B
+0x0e0 FlushTimer : Uint4B
+0x0e4 FlushThreshold : Uint4B
+0x0e8 ByteOffset : _LARGE_INTEGER
+0x0f0 MinimumBuffers : Uint4B
+0x0f4 BuffersAvailable : Int4B
+0x0f8 NumberOfBuffers : Int4B
+0x0fc MaximumBuffers : Uint4B
+0x100 EventsLost : Uint4B
......
+0x980 BufferCompressDuration : _LARGE_INTEGER
{
return __rdtsc();
}
{
PVOID *pEtwpGetCycleCount = (PVOID*)((ULONG_PTR)g.CkclWmiLoggerContext + 0x28);
PVOID Result = *pEtwpGetCycleCount;
*pEtwpGetCycleCount = TargetAddr;
return Result;
}
{
if (ExGetPreviousMode() == KernelMode) {
return __rdtsc();
}
// ......
return __rdtsc();
}
PVOID *StackFrame = (PVOID*)_AddressOfReturnAddress();
{
// 检查Syscall特有标志
if (*(ULONG*)StackCurrent != (ULONG)0x501802 || *(USHORT*)(StackCurrent - 1) != (USHORT)0xF33) {
continue;
}
// 往回遍历
for (StackCurrent--; StackCurrent < StackBase; ++StackCurrent)
{
PVOID CurrentPage = PAGE_ALIGN(*StackCurrent);
// 粗略用2个页的大小判断一下是否是Syscall调用
if (CurrentPage < g.SystemCallEntryPage ||
CurrentPage >= (PVOID)((ULONG_PTR)g.SystemCallEntryPage + PAGE_SIZE * 2)) {
continue;
}
// 到这里基本可以确定为Syscall事件了
}
}
SyscallDispatch(SyscallTarget);
if (g.OriginalNtOpenProcess == NULL) {
return STATUS_UNSUCCESSFUL;
}
{
if (*SyscallTarget == g.OriginalNtOpenProcess) {
*SyscallTarget = FakeNtOpenProcess;
}
}
{
if (ClientId->UniqueProcess == (HANDLE)123) {
LOG_INFO("Target process is being opened.", 0);
return STATUS_ACCESS_DENIED;
}
return g.OriginalNtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}
EventTraceControl(CKCL_TRACE_START);
}
InfinityHook 的执行流程图
总结
https://github.com/everdox/InfinityHook https://docs.microsoft.com/en-us/windows/win32/etw/about-event-tracing https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-the-nt-kernel-logger-session https://bbs.pediy.com/thread-223805.htm
- End -
看雪ID:Sprite雪碧
https://bbs.pediy.com/user-592310.htm
本文由看雪论坛 Sprite雪碧 原创
转载请注明来自看雪社区
往期热门回顾
1、Windows Kernel Exploit 内核漏洞学习(3)-任意内存覆盖漏洞
2、Windows Kernel Exploit 内核漏洞学习(2)-内核栈溢出
﹀
﹀
﹀
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com
↙点击下方“阅读原文”,查看更多干货