驱动开发:内核枚举DpcTimer定时器( 三 )

输出寻找CALL地址效果图如下:

驱动开发:内核枚举DpcTimer定时器

文章插图
第三步: 也是最重要的一步 , 在KiSetTimerEx里面,搜索特征,拿到里面的KiWaitNever(),KiWaitAlways()这两个函数地址 。
  • 488b05850c5100 KiWaitNever
  • 488b356b0e5100 KiWaitAlways
这个过程需要重复搜索,所以要把第一步和第二部过程归纳起来,具体代码如下所示 。
/*0: kd> uf KiSetTimerExnt!KiSetTimerEx:fffff807`70520a50 48895c2408movqword ptr [rsp+8],rbxfffff807`70520a55 48896c2410movqword ptr [rsp+10h],rbpfffff807`70520a5a 4889742418movqword ptr [rsp+18h],rsifffff807`70520a5f 57pushrdifffff807`70520a60 4154pushr12fffff807`70520a62 4155pushr13fffff807`70520a64 4156pushr14fffff807`70520a66 4157pushr15fffff807`70520a68 4883ec50subrsp,50hfffff807`70520a6c 488b05850c5100movrax,qword ptr [nt!KiWaitNever (fffff807`70a316f8)]fffff807`70520a73 488bf9movrdi,rcxfffff807`70520a76 488b356b0e5100movrsi,qword ptr [nt!KiWaitAlways (fffff807`70a318e8)]fffff807`70520a7d 410fb6e9movzxebp,r9b*/#include <ntddk.h>#include <ntstrsafe.h>// 得到KiProcessorBlock地址ULONG64 GetKeSetTimerEx(){// 获取 KeSetTimer 地址ULONG64 ul_KeSetTimer = 0;UNICODE_STRINGuc_KeSetTimer = { 0 };RtlInitUnicodeString(&uc_KeSetTimer, L"KeSetTimer");ul_KeSetTimer = (ULONG64)MmGetSystemRoutineAddress(&uc_KeSetTimer);if (ul_KeSetTimer == 0){return 0;}// 前 30 字节找 call 指令BOOLEAN b_e8 = FALSE;ULONG64 ul_e8Addr = 0;for (INT i = 0; i < 30; i++){// 验证地址是否可读写if (!MmIsAddressValid((PVOID64)ul_KeSetTimer)){continue;}// e8 0c 00 00 00 call nt!KiSetTimerEx (fffff807`70520a50)if (*(PUCHAR)(ul_KeSetTimer + i) == 0xe8){b_e8 = TRUE;ul_e8Addr = ul_KeSetTimer + i;break;}}// 找到 call 则解析目的地址if (b_e8 == TRUE){if (!MmIsAddressValid((PVOID64)ul_e8Addr)){return 0;}INT ul_callCode = *(INT*)(ul_e8Addr + 1);ULONG64 ul_KiSetTimerEx = ul_e8Addr + ul_callCode + 5;return ul_KiSetTimerEx;}return 0;}// 得到KiWaitNever地址ULONG64 GetKiWaitNever(ULONG64 address){// 验证地址是否可读写if (!MmIsAddressValid((PVOID64)address)){return 0;}// 前 100 字节找 找 KiWaitNeverfor (INT i = 0; i < 100; i++){// 48 8b 05 85 0c 51 00 | mov rax, qword ptr[nt!KiWaitNever(fffff807`70a316f8)]if (*(PUCHAR)(address + i) == 0x48 && *(PUCHAR)(address + i + 1) == 0x8b && *(PUCHAR)(address + i + 2) == 0x05){ULONG64 ul_movCode = *(UINT32*)(address + i + 3);ULONG64 ul_movAddr = address + i + ul_movCode + 7;// DbgPrint("找到KiWaitNever地址: %p \n", ul_movAddr);return ul_movAddr;}}return 0;}// 得到KiWaitAlways地址ULONG64 GetKiWaitAlways(ULONG64 address){// 验证地址是否可读写if (!MmIsAddressValid((PVOID64)address)){return 0;}// 前 100 字节找 找 KiWaitNeverfor (INT i = 0; i < 100; i++){// 48 8b 35 6b 0e 51 00 | mov rsi,qword ptr [nt!KiWaitAlways (fffff807`70a318e8)]if (*(PUCHAR)(address + i) == 0x48 && *(PUCHAR)(address + i + 1) == 0x8b && *(PUCHAR)(address + i + 2) == 0x35){ULONG64 ul_movCode = *(UINT32*)(address + i + 3);ULONG64 ul_movAddr = address + i + ul_movCode + 7;return ul_movAddr;}}return 0;}VOID UnDriver(PDRIVER_OBJECT driver){DbgPrint("卸载完成... \n");}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath){DbgPrint("hello lyshark.com \n");ULONG64 address = GetKeSetTimerEx();if (address != 0){ULONG64 KiWaitNeverAddress = GetKiWaitNever(address);DbgPrint("KiWaitNeverAddress = %p \n", KiWaitNeverAddress);ULONG64 KiWaitAlwaysAddress = GetKiWaitAlways(address);DbgPrint("KiWaitAlwaysAddress = %p \n", KiWaitAlwaysAddress);}Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;}运行这个程序,我们看下寻找到的地址是否与WinDBG中找到的地址一致 。
驱动开发:内核枚举DpcTimer定时器

文章插图
功能实现部分: 最后将这些功能整合在一起 , 循环输出链表元素,并解密元素即可实现枚举当前系统DPC定时器 。
代码核心API分析:
  • KeNumberProcessors 得到CPU数量(内核常量)
  • KeSetSystemAffinityThread 线程绑定到特定CPU上
  • GetKiProcessorBlock 获得KPRCB的地址
  • KeRevertToUserAffinityThread 取消绑定CPU
解密部分提取出KiWaitNeverKiWaitAlways用于解密计算,转换PKDPC对象结构,并输出即可 。
#include <Fltkernel.h>#include <ntddk.h>#include <intrin.h>typedef struct _KTIMER_TABLE_ENTRY{ ULONG_PTR Lock; LIST_ENTRY Entry; ULONG_PTR Time;}KTIMER_TABLE_ENTRY, *PKTIMER_TABLE_ENTRY;typedef struct _KTIMER_TABLE{ ULONG_PTR TimerExpiry[64]; KTIMER_TABLE_ENTRY TimerEntries[256];}KTIMER_TABLE, *PKTIMER_TABLE;BOOLEAN get_KiWait(PULONG64 never, PULONG64 always){ // 获取 KeSetTimer 地址 ULONG64 ul_KeSetTimer = 0; UNICODE_STRING uc_KeSetTimer = { 0 }; RtlInitUnicodeString(&uc_KeSetTimer, L"KeSetTimer"); ul_KeSetTimer = (ULONG64)MmGetSystemRoutineAddress(&uc_KeSetTimer); if (ul_KeSetTimer == NULL) {return FALSE; } // 前 30 字节找 KeSetTimer BOOLEAN b_e8 = FALSE; ULONG64 ul_e8Addr = 0; for (INT i = 0; i < 30; i++) {if (!MmIsAddressValid((PVOID64)ul_KeSetTimer)){continue;}/*0: kd> u nt!KeSetTimernt!KeSetTimer:fffff803`0fc63a40 4883ec38subrsp,38hfffff803`0fc63a44 4c89442420movqword ptr [rsp+20h],r8fffff803`0fc63a49 4533c9xorr9d,r9dfffff803`0fc63a4c 4533c0xorr8d,r8dfffff803`0fc63a4f e80c000000callnt!KiSetTimerEx (fffff803`0fc63a60)fffff803`0fc63a54 4883c438addrsp,38hfffff803`0fc63a58 c3retfffff803`0fc63a59 ccint3*/// fffff803`0fc63a4f e8 0c 00 00 00callnt!KiSetTimerEx (fffff803`0fc63a60)if (*(PUCHAR)(ul_KeSetTimer + i) == 0xe8){b_e8 = TRUE;ul_e8Addr = ul_KeSetTimer + i;break;} } // 找到 call 则解析目的地址 /*0: kd> u nt!KiSetTimerEx l20nt!KiSetTimerEx:fffff803`0fc63a60 48895c2408movqword ptr [rsp+8],rbxfffff803`0fc63a65 48896c2410movqword ptr [rsp+10h],rbpfffff803`0fc63a6a 4889742418movqword ptr [rsp+18h],rsifffff803`0fc63a6f 57pushrdifffff803`0fc63a70 4154pushr12fffff803`0fc63a72 4155pushr13fffff803`0fc63a74 4156pushr14fffff803`0fc63a76 4157pushr15fffff803`0fc63a78 4883ec50subrsp,50hfffff803`0fc63a7c 488b057d0c5100movrax,qword ptr [nt!KiWaitNever (fffff803`10174700)]fffff803`0fc63a83 488bf9movrdi,rcx */ ULONG64 ul_KiSetTimerEx = 0; if (b_e8 == TRUE) {if (!MmIsAddressValid((PVOID64)ul_e8Addr)){return FALSE;}INT ul_callCode = *(INT*)(ul_e8Addr + 1);ULONG64 ul_callAddr = ul_e8Addr + ul_callCode + 5;ul_KiSetTimerEx = ul_callAddr; } // 没有 call 则直接在当前函数找 else {ul_KiSetTimerEx = ul_KeSetTimer; } // 前 50 字节找 找 KiWaitNever 和 KiWaitAlways /* 0: kd> u nt!KiSetTimerEx l20 nt!KiSetTimerEx: fffff803`0fc63a60 48895c2408movqword ptr [rsp+8],rbx fffff803`0fc63a65 48896c2410movqword ptr [rsp+10h],rbp fffff803`0fc63a6a 4889742418movqword ptr [rsp+18h],rsi fffff803`0fc63a6f 57pushrdi fffff803`0fc63a70 4154pushr12 fffff803`0fc63a72 4155pushr13 fffff803`0fc63a74 4156pushr14 fffff803`0fc63a76 4157pushr15 fffff803`0fc63a78 4883ec50subrsp,50h fffff803`0fc63a7c 488b057d0c5100movrax,qword ptr [nt!KiWaitNever (fffff803`10174700)] fffff803`0fc63a83 488bf9movrdi,rcx fffff803`0fc63a86 488b35630e5100movrsi,qword ptr [nt!KiWaitAlways (fffff803`101748f0)] */ if (!MmIsAddressValid((PVOID64)ul_KiSetTimerEx)) {return FALSE; } // 存放 KiWaitNever 和 KiWaitAlways 的地址 ULONG64 ul_arr_ret[2]; // 对应 ul_arr_ret 的下标 INT i_sub = 0; for (INT i = 0; i < 50; i++) {// fffff803`0fc63a7c 488b057d0c5100movrax,qword ptr [nt!KiWaitNever (fffff803`10174700)]if (*(PUCHAR)(ul_KiSetTimerEx + i) == 0x48 && *(PUCHAR)(ul_KiSetTimerEx + i + 1) == 0x8b && *(PUCHAR)(ul_KiSetTimerEx + i + 6) == 0x00){ULONG64 ul_movCode = *(UINT32*)(ul_KiSetTimerEx + i + 3);ULONG64 ul_movAddr = ul_KiSetTimerEx + i + ul_movCode + 7;// 只拿符合条件的前两个值if (i_sub < 2){ul_arr_ret[i_sub++] = ul_movAddr;}} } *never = ul_arr_ret[0]; *always = ul_arr_ret[1]; return TRUE;}NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject){ return STATUS_SUCCESS;}NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath){ DbgPrint("hello lyshark.com \n"); // 获取 CPU 核心数 INT i_cpuNum = KeNumberProcessors; DbgPrint("CPU核心数: %d \n", i_cpuNum); for (KAFFINITY i = 0; i < i_cpuNum; i++) {// 线程绑定特定 CPUKeSetSystemAffinityThread(i + 1);// 获得 KPRCB 的地址ULONG64 p_PRCB = (ULONG64)__readmsr(0xC0000101) + 0x20;if (!MmIsAddressValid((PVOID64)p_PRCB)){return FALSE;}// 取消绑定 CPUKeRevertToUserAffinityThread();// 判断操作系统版本RTL_OSVERSIONINFOEXW OSVersion = { 0 };OSVersion.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);RtlGetVersion((PRTL_OSVERSIONINFOW)&OSVersion);// 计算 TimerTable 在 _KPRCB 结构中的偏移PKTIMER_TABLE p_TimeTable = NULL;if (OSVersion.dwMajorVersion == 10 && OSVersion.dwMinorVersion == 0){p_TimeTable = (PKTIMER_TABLE)(*(PULONG64)p_PRCB + 0x3680);}else if (OSVersion.dwMajorVersion == 6 && OSVersion.dwMinorVersion == 1){p_TimeTable = (PKTIMER_TABLE)(*(PULONG64)p_PRCB + 0x2200);}else{return FALSE;}// 遍历 TimerEntries[] 数组(大小 256)for (INT j = 0; j < 256; j++){// 获取 Entry 双向链表地址if (!MmIsAddressValid((PVOID64)p_TimeTable)){continue;}PLIST_ENTRY p_ListEntryHead = &(p_TimeTable->TimerEntries[j].Entry);// 遍历 Entry 双向链表for (PLIST_ENTRY p_ListEntry = p_ListEntryHead->Flink; p_ListEntry != p_ListEntryHead; p_ListEntry = p_ListEntry->Flink){// 根据 Entry 取 _KTIMER 对象地址if (!MmIsAddressValid((PVOID64)p_ListEntry)){continue;}PKTIMER p_Timer = CONTAINING_RECORD(p_ListEntry, KTIMER, TimerListEntry);// 硬编码取 KiWaitNever 和 KiWaitAlwaysULONG64 never = 0, always = 0;if (get_KiWait(&never, &always) == FALSE){return FALSE;}// 获取解密前的 Dpc 对象if (!MmIsAddressValid((PVOID64)p_Timer)){continue;}ULONG64 ul_Dpc = (ULONG64)p_Timer->Dpc;INT i_Shift = (*((PULONG64)never) & 0xFF);// 解密 Dpc 对象ul_Dpc ^= *((ULONG_PTR*)never);// 异或ul_Dpc = _rotl64(ul_Dpc, i_Shift);// 循环左移ul_Dpc ^= (ULONG_PTR)p_Timer;// 异或ul_Dpc = _byteswap_uint64(ul_Dpc);// 颠倒顺序ul_Dpc ^= *((ULONG_PTR*)always);// 异或// 对象类型转换PKDPC p_Dpc = (PKDPC)ul_Dpc;// 打印验证if (!MmIsAddressValid((PVOID64)p_Dpc)){continue;}DbgPrint("[LyShark] 定时器对象:0x%p | 函数入口:0x%p | 触发周期: %d \n ", p_Timer, p_Dpc->DeferredRoutine);}} } return STATUS_SUCCESS;}

推荐阅读