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

在操作系统内核中,DPC(Deferred Procedure Call)是一种延迟执行的过程调用机制,用于在中断服务例程(ISR)的上下文之外执行一些工作。DPC定时器是基于DPC机制的一种定时执行任务的方式。

DPC定时器的主要特点:

  1. 延迟执行: DPC定时器允许系统在未来的某个时间点执行一些操作,而不是立即执行。这对于一些需要在中断处理例程之外执行的任务很有用,以避免中断处理例程的执行时间过长。
  2. 定时器机制: DPC定时器是基于时间的机制,允许开发人员指定一个将来的时间点,当系统时间达到该时间点时,相关的DPC将被调度执行。这有助于实现定时任务,例如定期执行某个函数或操作。
  3. 中断上下文之外执行: DPC定时器通常在中断服务例程(ISR)之外执行,以避免在ISR中执行过长时间的任务,从而提高系统的响应性和稳定性。
  4. 任务调度: DPC定时器允许内核将需要延迟执行的任务排队,并在指定的时间点执行这些任务。这种任务调度机制有助于更有效地管理系统资源。

在笔者上一篇文章《内核枚举IoTimer定时器》中我们通过IoInitializeTimer这个API函数为跳板,向下扫描特征码获取到了IopTimerQueueHead也就是IO定时器的队列头,本章学习的枚举DPC定时器依然使用特征码扫描,唯一不同的是在新版系统中DPC是被异或加密的,想要找到正确的地址,只是需要在找到DPC表头时进行解密操作即可。

DPC定时器的作用是什么?

DPC(Deferred Procedure Call)是一种异步执行的机制。它允许内核代码在不中断当前进程的情况下,延迟执行一些工作。DPC的执行是由内核定时器触发的。内核定时器是一种特殊的内核对象,用于定时执行某个特定的操作。在DPC的上下文中,内核可以安全地访问任何内核数据结构,而不会引起死锁或其他问题。

在内核中可以使用DPC定时器设置任意定时任务,当到达某个节点时自动触发定时回调,定时器的内部使用KTIMER对象,当设置任务时会自动插入到DPC队列,由操作系统循环读取DPC队列并执行任务,枚举DPC定时器可得知系统中存在的DPC任务。

要想在新版系统中得到DPC定时器则需要执行的步骤有哪些?

  • 1.找到KiProcessorBlock地址并解析成_KPRCB结构
  • 2.在_KPRCB结构中得到_KTIMER_TABLE偏移
  • 3.解析_KTIMER_TABLE_ENTRY得到加密后的双向链表

首先_KPRCB这个结构体与CPU内核对应,获取方式可通过一个未导出的变量nt!KiProcessorBlock来得到,如下双核电脑,结构体存在两个与之对应的结构地址。

kd> dq nt!KiProcessorBlock
fffff807`70a32cc0  fffff807`6f77c180 ffffbe81`3cee0180
fffff807`70a32cd0  00000000`00000000 00000000`00000000
fffff807`70a32ce0  00000000`00000000 00000000`00000000

KiProcessorBlock是一个数组,其第一个结构体TimerTable则是结构体的偏移。

kd> dt _KPRCB fffff807`6f77c180
ntdll!_KPRCB+0x000 MxCsr            : 0x1f80+0x3680 TimerTable       : _KTIMER_TABLE (此处)+0x5880 DpcGate          : _KGATE

接下来是把所有的KTIMER都枚举出来,KTIMER在TimerTable中的存储方式是数组+双向链表。

kd> dt _KTIMER_TABLE
ntdll!_KTIMER_TABLE+0x000 TimerExpiry      : [64] Ptr64 _KTIMER+0x200 TimerEntries     : [256] _KTIMER_TABLE_ENTRY (此处)

到了_KTIMER_TABLE_ENTRY这里,Entry开始的双向链表,每一个元素都对应一个Timer也就是说我们已经可以遍历所有未解密的Time变量了。

kd> dt _KTIMER_TABLE_ENTRY 0xfffff807`6f77c180 + 0x3680
ntdll!_KTIMER_TABLE_ENTRY+0x000 Lock             : 0+0x008 Entry            : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]+0x018 Time             : _ULARGE_INTEGER 0x0kd> dt _KTIMER_TABLE_ENTRY 0xfffff807`6f77c180 + 0x3680 + 0x200
ntdll!_KTIMER_TABLE_ENTRY+0x000 Lock             : 0+0x008 Entry            : _LIST_ENTRY [ 0xffffa707`a0d3e1a0 - 0xffffa707`a0d3e1a0 ]+0x018 Time             : _ULARGE_INTEGER 0x00000001`a8030353

至于如何解密,我们需要得到加密位置,如下通过KeSetTimer找到KeSetTimerEx从中得到DCP加密流程。

kd> u nt!KeSetTimer
nt!KeSetTimer:
fffff803`0fc63a40 4883ec38        sub     rsp,38h
fffff803`0fc63a44 4c89442420      mov     qword ptr [rsp+20h],r8
fffff803`0fc63a49 4533c9          xor     r9d,r9d
fffff803`0fc63a4c 4533c0          xor     r8d,r8d
fffff803`0fc63a4f e80c000000      call    nt!KiSetTimerEx (fffff803`0fc63a60)
fffff803`0fc63a54 4883c438        add     rsp,38h
fffff803`0fc63a58 c3              ret
fffff803`0fc63a59 cc              int     3kd> u nt!KiSetTimerEx l50
nt!KiSetTimerEx:
fffff803`0fc63a60 48895c2408      mov     qword ptr [rsp+8],rbx
fffff803`0fc63a65 48896c2410      mov     qword ptr [rsp+10h],rbp
fffff803`0fc63a6a 4889742418      mov     qword ptr [rsp+18h],rsi
fffff803`0fc63a6f 57              push    rdi
fffff803`0fc63a70 4154            push    r12
fffff803`0fc63a72 4155            push    r13
fffff803`0fc63a74 4156            push    r14
fffff803`0fc63a76 4157            push    r15
fffff803`0fc63a78 4883ec50        sub     rsp,50h
fffff803`0fc63a7c 488b057d0c5100  mov     rax,qword ptr [nt!KiWaitNever (fffff803`10174700)]
fffff803`0fc63a83 488bf9          mov     rdi,rcx
fffff803`0fc63a86 488b35630e5100  mov     rsi,qword ptr [nt!KiWaitAlways (fffff803`101748f0)]
fffff803`0fc63a8d 410fb6e9        movzx   ebp,r9b
fffff803`0fc63a91 4c8bac24a0000000 mov     r13,qword ptr [rsp+0A0h]
fffff803`0fc63a99 458bf8          mov     r15d,r8d
fffff803`0fc63a9c 4933f5          xor     rsi,r13
fffff803`0fc63a9f 488bda          mov     rbx,rdx
fffff803`0fc63aa2 480fce          bswap   rsi
fffff803`0fc63aa5 4833f1          xor     rsi,rcx
fffff803`0fc63aa8 8bc8            mov     ecx,eax
fffff803`0fc63aaa 48d3ce          ror     rsi,cl
fffff803`0fc63aad 4833f0          xor     rsi,rax
fffff803`0fc63ab0 440f20c1        mov     rcx,cr8
fffff803`0fc63ab4 48898c24a0000000 mov     qword ptr [rsp+0A0h],rcx
fffff803`0fc63abc b802000000      mov     eax,2
fffff803`0fc63ac1 440f22c0        mov     cr8,rax
fffff803`0fc63ac5 8b05dd0a5100    mov     eax,dword ptr [nt!KiIrqlFlags (fffff803`101745a8)]
fffff803`0fc63acb 85c0            test    eax,eax
fffff803`0fc63acd 0f85b72d1a00    jne     nt!KiSetTimerEx+0x1a2e2a (fffff803`0fe0688a)
fffff803`0fc63ad3 654c8b342520000000 mov   r14,qword ptr gs:[20h]
fffff803`0fc63adc 33d2            xor     edx,edx
fffff803`0fc63ade 488bcf          mov     rcx,rdi
fffff803`0fc63ae1 e86aa2fdff      call    nt!KiCancelTimer (fffff803`0fc3dd50)
fffff803`0fc63ae6 440fb6e0        movzx   r12d,al
fffff803`0fc63aea 48897730        mov     qword ptr [rdi+30h],rsi
fffff803`0fc63aee 33c0            xor     eax,eax
fffff803`0fc63af0 44897f3c        mov     dword ptr [rdi+3Ch],r15d
fffff803`0fc63af4 8b0f            mov     ecx,dword ptr [rdi]
fffff803`0fc63af6 4889442430      mov     qword ptr [rsp+30h],rax
fffff803`0fc63afb 894c2430        mov     dword ptr [rsp+30h],ecx
fffff803`0fc63aff 488bcb          mov     rcx,rbx
fffff803`0fc63b02 48c1e920        shr     rcx,20h
fffff803`0fc63b06 4889442438      mov     qword ptr [rsp+38h],rax
fffff803`0fc63b0b 4889442440      mov     qword ptr [rsp+40h],rax
fffff803`0fc63b10 40886c2431      mov     byte ptr [rsp+31h],bpl
fffff803`0fc63b15 85c9            test    ecx,ecx
fffff803`0fc63b17 0f89c0000000    jns     nt!KiSetTimerEx+0x17d (fffff803`0fc63bdd)
fffff803`0fc63b1d 33c9            xor     ecx,ecx
fffff803`0fc63b1f 8bd1            mov     edx,ecx
fffff803`0fc63b21 40f6c5fc        test    bpl,0FCh
fffff803`0fc63b25 0f85a3000000    jne     nt!KiSetTimerEx+0x16e (fffff803`0fc63bce)
fffff803`0fc63b2b 48894c2420      mov     qword ptr [rsp+20h],rcx
fffff803`0fc63b30 48b80800000080f7ffff mov rax,0FFFFF78000000008h
fffff803`0fc63b3a 4d8bc5          mov     r8,r13
fffff803`0fc63b3d 488b00          mov     rax,qword ptr [rax]
fffff803`0fc63b40 804c243340      or      byte ptr [rsp+33h],40h
fffff803`0fc63b45 482bc3          sub     rax,rbx
fffff803`0fc63b48 48894718        mov     qword ptr [rdi+18h],rax
fffff803`0fc63b4c 4803c2          add     rax,rdx
fffff803`0fc63b4f 48c1e812        shr     rax,12h
fffff803`0fc63b53 488bd7          mov     rdx,rdi
fffff803`0fc63b56 440fb6c8        movzx   r9d,al
fffff803`0fc63b5a 44884c2432      mov     byte ptr [rsp+32h],r9b
fffff803`0fc63b5f 8b442430        mov     eax,dword ptr [rsp+30h]
fffff803`0fc63b63 8907            mov     dword ptr [rdi],eax
fffff803`0fc63b65 894f04          mov     dword ptr [rdi+4],ecx
fffff803`0fc63b68 498bce          mov     rcx,r14
fffff803`0fc63b6b e8209ffdff      call    nt!KiInsertTimerTable (fffff803`0fc3da90)
fffff803`0fc63b70 84c0            test    al,al
fffff803`0fc63b72 0f8495000000    je      nt!KiSetTimerEx+0x1ad (fffff803`0fc63c0d)
fffff803`0fc63b78 f7058608510000000200 test dword ptr [nt!PerfGlobalGroupMask+0x8 (fffff803`10174408)],20000h
fffff803`0fc63b82 0f852f2d1a00    jne     nt!KiSetTimerEx+0x1a2e57 (fffff803`0fe068b7)
fffff803`0fc63b88 f081277fffffff  lock and dword ptr [rdi],0FFFFFF7Fh
fffff803`0fc63b8f 488b8424a0000000 mov     rax,qword ptr [rsp+0A0h]
fffff803`0fc63b97 4533c9          xor     r9d,r9d
fffff803`0fc63b9a 33d2            xor     edx,edx
fffff803`0fc63b9c 88442420        mov     byte ptr [rsp+20h],al
fffff803`0fc63ba0 498bce          mov     rcx,r14
fffff803`0fc63ba3 458d4101        lea     r8d,[r9+1]
fffff803`0fc63ba7 e8044efeff      call    nt!KiExitDispatcher (fffff803`0fc489b0)

如上汇编代码KiSetTimerEx中就是DPC加密细节,如果需要解密只需要逆操作即可,此处我就具体分析下加密细节,分析这个东西我建议你使用记事本带着色的。

分析思路是这样的,首先这里要传入待加密的DPC数据,然后经过KiWaitNeverKiWaitAlways对数据进行xor,ror,bswap等操作。

将如上所示的汇编解密流程通过C语言的方式实现,解密函数DPC_Print过程可以被总结为如下几个流程:

  • 首先获取定时器结构体中Dpc成员的地址,将其转换为ULONG_PTR类型的指针ptrDpc

  • 然后将ptrDpc异或上一个常量p2dq(ptrKiWaitNever),这个常量是KiWaitNever的指针地址强制转换为ULONG_PTR类型后的结果,相当于异或上一个随机值来进行简单的加密。

  • 然后将ptrDpc循环左移nShift位,其中nShift的值为KiWaitNever指针值的低8位,即取最后一个字节。

  • 接着将ptrDpc异或上定时器结构体的地址,相当于对加密结果进行一个简单的混淆。然后对ptrDpc进行字节交换,相当于将ptrDpc的字节序进行翻转,以便在后面的代码中能够正确地解密DPC结构体。最后将ptrDpc异或上一个常量p2dq(ptrKiWaitAlways),这个常量是KiWaitAlways的指针地址强制转换为ULONG_PTR类型后的结果,相当于再进行一次简单的加密。

最后,如果解密得到的DPC结构体指针DecDpc是一个有效的内核地址,就输出该DPC的地址和它的延迟函数地址。其中DeferredRoutine是KDPC结构体的一个成员,用于保存DPC的回调函数地址,将上述流程通过代码方式实现则如下所示;

#include <ntddk.h>
#include <ntstrsafe.h>// 解密DPC
void DPC_Print(PKTIMER ptrTimer)
{ULONG_PTR ptrDpc = (ULONG_PTR)ptrTimer->Dpc;KDPC* DecDpc = NULL;DWORD nShift = (p2dq(ptrKiWaitNever) & 0xFF);// _RSI->Dpc = (_KDPC *)v19;// _RSI = Timer;ptrDpc ^= p2dq(ptrKiWaitNever);        // v19 = KiWaitNever ^ v18;ptrDpc = _rotl64(ptrDpc, nShift);      // v18 = __ROR8__((unsigned __int64)Timer ^ _RBX, KiWaitNever);ptrDpc ^= (ULONG_PTR)ptrTimer;ptrDpc = _byteswap_uint64(ptrDpc);     // __asm { bswap   rbx }ptrDpc ^= p2dq(ptrKiWaitAlways);       // _RBX = (unsigned __int64)DPC ^ KiWaitAlways;// real DPCif (MmIsAddressValid((PVOID)ptrDpc)){DecDpc = (KDPC*)ptrDpc;DbgPrint("DPC = %p | routine = %p \n", DecDpc, DecDpc->DeferredRoutine);}
}VOID UnDriver(PDRIVER_OBJECT driver)
{DbgPrint("卸载完成... \n");
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark");PKTIMER ptrTimer = NULL;DPC_Print(ptrTimer);Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

接着将这些功能通过代码实现,首先得到我们需要的函数地址,这些地址包括。

ULONG_PTR ptrKiProcessorBlock = 0xfffff80770a32cc0;
ULONG_PTR ptrOffsetKTimerTable = 0x3680;
ULONG_PTR ptrKiWaitNever = 0xfffff80770a316f8;
ULONG_PTR ptrKiWaitAlways = 0xfffff80770a318e8;

此处我把它分为三步走,第一步找到KiProcessorBlock函数地址,第二步找到KeSetTimer并从里面寻找KeSetTimerEx,第三步根据KiSetTimerEx地址,搜索到KiWaitNever(),KiWaitAlways()这两个函数内存地址,最终循环链表并解密DPC队列。

寻找KiProcessorBlock地址

找到KiProcessorBlock函数地址,该地址可通过__readmsr()寄存器相加偏移得到。

在WinDBG中可以输入rdmsr c0000082得到MSR地址。

MSR寄存器使用代码获取也是很容易,只要找到MSR地址在加上0x20即可得到KiProcessorBlock的地址了。

/*lyshark 0: kd> dp !KiProcessorBlockfffff807`70a32cc0  fffff807`6f77c180 ffffbe81`3cee0180fffff807`70a32cd0  00000000`00000000 00000000`00000000fffff807`70a32ce0  00000000`00000000 00000000`00000000fffff807`70a32cf0  00000000`00000000 00000000`00000000fffff807`70a32d00  00000000`00000000 00000000`00000000fffff807`70a32d10  00000000`00000000 00000000`00000000fffff807`70a32d20  00000000`00000000 00000000`00000000fffff807`70a32d30  00000000`00000000 00000000`00000000
*/#include <ntddk.h>
#include <ntstrsafe.h>// 得到KiProcessorBlock地址
ULONG64 GetKiProcessorBlock()
{ULONG64 PrcbAddress = 0;PrcbAddress = (ULONG64)__readmsr(0xC0000101) + 0x20;if (PrcbAddress != 0){// PrcbAddress 是一个地址 这个地址存放了某个 CPU 的 _KPRCB 的地址return *(ULONG_PTR*)PrcbAddress;}return 0;
}VOID UnDriver(PDRIVER_OBJECT driver)
{DbgPrint("卸载完成... \n");
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");ULONG64 address = GetKiProcessorBlock();if (address != 0){DbgPrint("KiProcessorBlock = %p \n", address);}Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

运行后即可得到输出效果如下:

寻找KeSetTimer地址

找到KeSetTimer从里面搜索特征得到call KeSetTimerEx函数地址,还记得《内核枚举IoTimer定时器》中我们采用的特征码定位方式吗,没错本次还要使用这个方法,我们此处需要搜索到e80c000000这段特征。

/*lyshark 0: kd> uf KeSetTimernt!KeSetTimer:fffff807`70520a30 4883ec38        sub     rsp,38hfffff807`70520a34 4c89442420      mov     qword ptr [rsp+20h],r8fffff807`70520a39 4533c9          xor     r9d,r9dfffff807`70520a3c 4533c0          xor     r8d,r8dfffff807`70520a3f e80c000000      call    nt!KiSetTimerEx (fffff807`70520a50)fffff807`70520a44 4883c438        add     rsp,38hfffff807`70520a48 c3              ret
*/#include <ntddk.h>
#include <ntstrsafe.h>// 得到KiProcessorBlock地址
ULONG64 GetKeSetTimerEx()
{// 获取 KeSetTimer 地址ULONG64 ul_KeSetTimer = 0;UNICODE_STRING  uc_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;
}VOID UnDriver(PDRIVER_OBJECT driver)
{DbgPrint("卸载完成... \n");
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");ULONG64 address = GetKeSetTimerEx();if (address != 0){DbgPrint("KeSetTimerEx = %p \n", address);}Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

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

寻找KiWaitNever和KiWaitAlways地址

也是最重要的一步,在KiSetTimerEx里面,搜索特征,拿到里面的KiWaitNever(),KiWaitAlways()这两个函数地址。

  • 488b05850c5100 KiWaitNever
  • 488b356b0e5100 KiWaitAlways

这个过程需要重复搜索,所以要把第一步和第二部过程归纳起来,具体代码如下所示。

/*0: kd> uf KiSetTimerExnt!KiSetTimerEx:fffff807`70520a50 48895c2408      mov     qword ptr [rsp+8],rbxfffff807`70520a55 48896c2410      mov     qword ptr [rsp+10h],rbpfffff807`70520a5a 4889742418      mov     qword ptr [rsp+18h],rsifffff807`70520a5f 57              push    rdifffff807`70520a60 4154            push    r12fffff807`70520a62 4155            push    r13fffff807`70520a64 4156            push    r14fffff807`70520a66 4157            push    r15fffff807`70520a68 4883ec50        sub     rsp,50hfffff807`70520a6c 488b05850c5100  mov     rax,qword ptr [nt!KiWaitNever (fffff807`70a316f8)]fffff807`70520a73 488bf9          mov     rdi,rcxfffff807`70520a76 488b356b0e5100  mov     rsi,qword ptr [nt!KiWaitAlways (fffff807`70a318e8)]fffff807`70520a7d 410fb6e9        movzx   ebp,r9b
*/#include <ntddk.h>
#include <ntstrsafe.h>// 得到KiProcessorBlock地址
ULONG64 GetKeSetTimerEx()
{// 获取 KeSetTimer 地址ULONG64 ul_KeSetTimer = 0;UNICODE_STRING  uc_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 \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>#define IRP_TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)UNICODE_STRING name_device;         // 设备名
UNICODE_STRING name_symbol;         // 符号链接
PDEVICE_OBJECT deviceObj;           // 设备对象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 字节找 call 指令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 4883ec38        sub     rsp,38hfffff803`0fc63a44 4c89442420      mov     qword ptr [rsp+20h],r8fffff803`0fc63a49 4533c9          xor     r9d,r9dfffff803`0fc63a4c 4533c0          xor     r8d,r8dfffff803`0fc63a4f e80c000000      call    nt!KiSetTimerEx (fffff803`0fc63a60)fffff803`0fc63a54 4883c438        add     rsp,38hfffff803`0fc63a58 c3              retfffff803`0fc63a59 cc              int     3*/// fffff803`0fc63a4f e8 0c 00 00 00      call    nt!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 48895c2408      mov     qword ptr [rsp+8],rbxfffff803`0fc63a65 48896c2410      mov     qword ptr [rsp+10h],rbpfffff803`0fc63a6a 4889742418      mov     qword ptr [rsp+18h],rsifffff803`0fc63a6f 57              push    rdifffff803`0fc63a70 4154            push    r12fffff803`0fc63a72 4155            push    r13fffff803`0fc63a74 4156            push    r14fffff803`0fc63a76 4157            push    r15fffff803`0fc63a78 4883ec50        sub     rsp,50hfffff803`0fc63a7c 488b057d0c5100  mov     rax,qword ptr [nt!KiWaitNever (fffff803`10174700)]fffff803`0fc63a83 488bf9          mov     rdi,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 l20nt!KiSetTimerEx:fffff803`0fc63a60 48895c2408      mov     qword ptr [rsp+8],rbxfffff803`0fc63a65 48896c2410      mov     qword ptr [rsp+10h],rbpfffff803`0fc63a6a 4889742418      mov     qword ptr [rsp+18h],rsifffff803`0fc63a6f 57              push    rdifffff803`0fc63a70 4154            push    r12fffff803`0fc63a72 4155            push    r13fffff803`0fc63a74 4156            push    r14fffff803`0fc63a76 4157            push    r15fffff803`0fc63a78 4883ec50        sub     rsp,50hfffff803`0fc63a7c 488b057d0c5100  mov     rax,qword ptr [nt!KiWaitNever (fffff803`10174700)]fffff803`0fc63a83 488bf9          mov     rdi,rcxfffff803`0fc63a86 488b35630e5100  mov     rsi,qword ptr [nt!KiWaitAlways (fffff803`101748f0)]*/if (!MmIsAddressValid((PVOID64)ul_KiSetTimerEx)){return FALSE;}ULONG64 ul_arr_ret[2];          // 存放 KiWaitNever 和 KiWaitAlways 的地址INT i_sub = 0;                  // 对应 ul_arr_ret 的下标for (INT i = 0; i < 50; i++){// // fffff803`0fc63a7c 488b057d0c5100  mov     rax,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;
}BOOLEAN EnumDpc()
{DbgPrint("hello lyshark \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();// 计算 TimerTable 在 _KPRCB 结构中的偏移PKTIMER_TABLE p_TimeTable = NULL;// Windows 10 得到_KPRCB + 0x3680p_TimeTable = (PKTIMER_TABLE)(*(PULONG64)p_PRCB + 0x3680);// 遍历 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 和 KiWaitAlways ULONG64 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("定时器对象:0x%p | 函数入口:0x%p | 触发周期: %d \n ", p_Timer, p_Dpc->DeferredRoutine, p_Timer->Period);}}}return TRUE;
}// 对应 IRP_MJ_DEVICE_CONTROL
NTSTATUS myIrpControl(IN PDEVICE_OBJECT pDevObj, IN PIRP pIRP)
{// 获取 IRP 对应的 I/O 堆栈指针PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIRP);// 得到输入缓冲区大小ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength;// 得到输出缓冲区大小ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength;// 得到 IOCTL 码ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;// 捕获 I/O 操作类型(MajorFunction)switch (code){case IRP_TEST:{break;}default:break;}// 完成 IO 请求IoCompleteRequest(pIRP, IO_NO_INCREMENT);return STATUS_SUCCESS;
}// 对应 IRP_MJ_CREATE 、 IRP_MJ_CLOSE
NTSTATUS dpc_CAC(IN PDEVICE_OBJECT pDevObj, IN PIRP pIRP)
{// 将 IRP 返回给 I/O 管理器IoCompleteRequest(pIRP,               // IRP 指针IO_NO_INCREMENT     // 线程优先级,IO_NO_INCREMENT :不增加优先级);// 设置 I/O 请求状态pIRP->IoStatus.Status = STATUS_SUCCESS;// 设置 I/O 请求传输的字节数pIRP->IoStatus.Information = 0;return STATUS_SUCCESS;
}NTSTATUS CreateDevice(IN PDRIVER_OBJECT DriverObject)
{// 定义返回值NTSTATUS status;// 初始化设备名RtlInitUnicodeString(&name_device, L"\\Device\\LySharkDriver");// 创建设备status = IoCreateDevice(DriverObject,                           // 指向驱动对象的指针0,                                      // 设备扩展分配的字节数&name_device,                           // 设备名FILE_DEVICE_UNKNOWN,                    // 设备类型0,                                      // 驱动设备附加信息TRUE,                                   // 设备对象是否独占设备&deviceObj                              // 设备对象指针 );if (!NT_SUCCESS(status)){return status;}// 初始化符号链接名RtlInitUnicodeString(&name_symbol, L"\\??\\LySharkDriver");// 创建符号链接status = IoCreateSymbolicLink(&name_symbol, &name_device);if (!NT_SUCCESS(status)){return status;}return STATUS_SUCCESS;
}NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)
{// 定义返回值NTSTATUS status;// 删除符号链接status = IoDeleteSymbolicLink(&name_symbol);if (!NT_SUCCESS(status)){return status;}// 删除设备IoDeleteDevice(deviceObj);return STATUS_SUCCESS;
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{// 定义返回值NTSTATUS status;// 指定驱动卸载函数DriverObject->DriverUnload = (PDRIVER_UNLOAD)DriverUnload;// 指定派遣函数DriverObject->MajorFunction[IRP_MJ_CREATE] = dpc_CAC;DriverObject->MajorFunction[IRP_MJ_CLOSE] = dpc_CAC;DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = myIrpControl;// 创建设备status = CreateDevice(DriverObject);if (!NT_SUCCESS(status)){return status;}// 执行枚举EnumDpc();return STATUS_SUCCESS;
}

最终运行枚举程序,你将会看到系统中所有的定时器,与ARK工具对比是一致的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171982.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Namecheap怎么样,Namecheap优惠码以及注册手把手教程

Namecheap 是一家成熟的服务器域名托管公司&#xff0c;可以为合适的客户提供良好的解决方案。这些优点和缺点应该让您清楚地了解您的期望&#xff0c;以便您知道这是否是您网站的正确选择。 Namecheap怎么样&#xff1f; 已成立的公司&#xff1a; Namecheap 已经营 20 多年…

【代码随想录刷题】Day18 二叉树05------延伸题目练习

文章目录 1.【113】路径总和II1.1 题目描述1.2 解题思路1.3 java代码实现 2.【105】从前序与中序遍历序列构造二叉树2.1 题目描述2.2 java代码实现 【113】路径总和II 【105】从前序与中序遍历序列构造二叉树 1.【113】路径总和II 1.1 题目描述 给你二叉树的根节点 root 和一…

Vscode工具使用指南

通用 快捷键文件 / 编辑查找 / 替换窗口插件主题 连接linux 快捷键 文件 / 编辑 新建文件&#xff1a;CtrlN放大或缩小&#xff1a;Ctrl /-代码行缩进&#xff0c;展开&#xff1a;Ctrl[ 和 Ctrl]在当前行下方插入一行&#xff1a;CtrlEnter在当前行上方插入一行&#xff1a;…

EMQX-5.3.1单机集群部署并基于Nginx实现负载均衡

本例单机集群部署使用三个节点&#xff0c;分别为node1、node2、node3 一、安装与配置 1 创建数据目录 mkdir -p node1/data node1/logs mkdir -p node2/data node2/logs mkdir -p mode3/data node3/logs 2 数据目录授权 chown 1000 node1/ node2/ node3/ chown 1000 n…

jsp生成验证码的代码

效果图&#xff1a; loginProcess.jsp <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%><% String captcharequest.getParameter("captcha");%><% String captcha_session(String)s…

MySQL基本SQL语句(上)

MySQL基本SQL语句&#xff08;上&#xff09; 一、客户端工具的使用 1、客户端工具mysql使用 mysql: mysql命令行工具&#xff0c;一般用来连接访问mysql数据库 选项说明-u, --username指定登录用户名-p, --password指定登录密码(注意是小写p),一定要放到最后面-h, --hostn…

HDFS JAVA API的应用

首先把hadoop服务起来 1. (简答题) 使用HDFS 的JAVA API 进行编程&#xff1a; &#xff08;1&#xff09;获取自己HDFS集群下的所有文件和目录&#xff1b; //获取自己HDFS集群下的所有文件和目录&#xff1b;import org.apache.hadoop.conf.Configuration; import org.apa…

究竟FactoryBean是什么?深入理解Spring的工厂神器

文章目录 前言什么是FactoryBean&#xff1f;如何使用FactoryBean&#xff1f;我们常见的FactoryBeanBeanFactory 和 FactoryBean&#xff1f;FactoryBean后续&#xff1f;MapperFactoryBean 前言 在Spring框架中&#xff0c;bean的创建通常交由Spring IoC容器负责&#xff0c…

【从亮机卡开始的云炼丹】环境配置记录debug

要更改Anaconda环境的默认路径到D盘 可以按照以下步骤操作&#xff1a; 1. 打开Anaconda Prompt&#xff08;或者命令行窗口&#xff09;。 2. 输入以下命令更改Anaconda环境的默认路径到D盘&#xff1a; conda config --set envs_dirs D:\Anaconda\envs 这将把Anaconda环境…

汽车租聘管理与推荐系统Python+Django网页界面+协同过滤推荐算法

一、介绍 汽车租聘管理与推荐系统。本系统使用Python作为主要编程语言&#xff0c;前端采用HTML、CSS、BootStrap等技术搭建前端界面&#xff0c;后端采用Django框架处理用户的请求。创新点&#xff1a;使用协同过滤推荐算法实现对当前用户个性化推荐。 其主要功能如下&#x…

设计模式—依赖倒置原则(DIP)

1.概念 依赖倒置原则&#xff08;Dependence Inversion Principle&#xff09;是程序要依赖于抽象接口&#xff0c;不要依赖于具体实现。简单的说就是要求对抽象进行编程&#xff0c;不要对实现进行编程&#xff0c;这样就降低了客户与实现模块间的耦合。 通俗的讲&#xff1…

SpringBoot校验List失效解决方法

文章目录 SpringBoot校验List失效解决方法附&#xff1a;校验基本数据类型和String类型的方法参数时也需要在类上加Validated SpringBoot校验List失效解决方法 失效场景示例代码&#xff1a; RestController RequestMapping("/v1/jx/flowSummary") Slf4j public cl…

抖音小店开店指南:流程、准备和营销策略一站解析

抖音小店已成为一个热门的社交电商平台&#xff0c;为商家提供了一个快速、方便、低成本的开店通道。下面四川不若与众将介绍抖音小店开店的流程和需要准备的工作&#xff0c;帮助商家顺利开启自己的电商之路。 一、开店准备工作&#xff1a; 1. 产品准备&#xff1a;确定出售…

Java王者荣耀

一、创建项目 二、代码 package com.sxt;import javax.swing.*; import java.awt.*;public class Background extends GameObject {public Background(GameFrame gameFrame) {super(gameFrame);// TODO Auto-generated constructor stub}Image bg Toolkit.getDefaultToolkit(…

Mac 最佳使用指南

如何在macOS系统安装根证书mac Terminal config proxy 【mac 终端配置代理】iPhone 安装 iOS 17公测版&#xff08;Public Beta)macOS 最佳命令行客户端&#xff1a;iTermMac 配置与 Linux 互信Mac mini 外接移动硬盘无法写入或者无法显示的解决方法如何在 macOS 美化 iterm2 &…

数据库基础教程之数据库的创建(二)

双击打开Navicat,点击:文件-》新建连接-》PostgreSQL 在下图新建连接中输入各参数,然后点击:连接测试,连接成功后再点击确定。 创建数据表   3.1 方法1   3.1.1.双击你的数据库-》双击public-》双击选中表-》右键-》新建表-》常规 3.1.2.设置字段信息   双击选中创建…

C++ vector迭代器失效

STL中vector迭代器失效常见错误写法示例 最近在看STL容器失效的例子&#xff0c;涉及到vector数组迭代器失效的问题&#xff0c;如果不注意使用&#xff0c;很容易出现问题&#xff0c;我们先来看一下一个简单的示例程序&#xff0c;在数组nums中删除大于50的元素&#xff0c;…

【方块消消乐】方块消除游戏-微信小程序开发流程详解

有做过俄罗斯方块游戏小程序的经验&#xff0c;这次有做了一个消灭方块的游戏&#xff0c;实现过程很顺利&#xff0c;游戏看着和之前做的俄罗斯方块游戏很像&#xff0c;这里调整了玩法&#xff0c;试玩感觉还可以&#xff0c;接下来给大家讲一讲消灭方块游戏开发过程。 俄罗斯…

(离散数学)命题逻辑推理一:直接推理

P说明这一行是前提&#xff0c;T说明这一行是结论 &#xff0c;I说明该结论是由推导而来&#xff0c;E说明该结论是由化简而来&#xff0c;括号里的数字是推导这一结论需要的条件序号。 这种写法只是将重言蕴含的论证的思路进行了梳理 &#xff0c;前件为真则后件为真、后件为假…

【深度学习】DAMO-YOLO,阿里,701类通用检测模型,目标检测

https://github.com/tinyvision/DAMO-YOLO/blob/master/README_cn.md DAMO-YOLO是由阿里巴巴达摩院智能计算实验室TinyML团队开发的一个兼顾速度与精度的目标检测框架,其效果超越了目前的一众YOLO系列方法&#xff0c;在实现SOTA的同时&#xff0c;保持了很高的推理速度。DAMO…