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

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

要想在新版系统中得到DPC定时器则需要执行以下步骤

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

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

lyshark.com 0: kd> dq nt!KiProcessorBlock
fffff807`70a32cc0 fffff807`6f77c180 ffffbe81`3cee0180
fffff807`70a32cd0 00000000`00000000 00000000`00000000
fffff807`70a32ce0 00000000`00000000 00000000`00000000

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

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

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

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

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

lyshark.com 0: 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 0x0 lyshark.com 0: kd> 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加密流程。

lyshark.com 0: 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 3 0: kd> 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等操作。

将解密流程通过代码的方式实现。

#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 DPC
if (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.com"); 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函数地址,该地址可通过__readmsr()寄存器相加偏移得到。

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

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

/*
lyshark.com 0: kd> dp !KiProcessorBlock
fffff807`70a32cc0 fffff807`6f77c180 ffffbe81`3cee0180
fffff807`70a32cd0 00000000`00000000 00000000`00000000
fffff807`70a32ce0 00000000`00000000 00000000`00000000
fffff807`70a32cf0 00000000`00000000 00000000`00000000
fffff807`70a32d00 00000000`00000000 00000000`00000000
fffff807`70a32d10 00000000`00000000 00000000`00000000
fffff807`70a32d20 00000000`00000000 00000000`00000000
fffff807`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.com \n"); ULONG64 address = GetKiProcessorBlock();
if (address != 0)
{
DbgPrint("KiProcessorBlock = %p \n", address);
} Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

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

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

/*
lyshark.com 0: kd> uf KeSetTimer
nt!KeSetTimer:
fffff807`70520a30 4883ec38 sub rsp,38h
fffff807`70520a34 4c89442420 mov qword ptr [rsp+20h],r8
fffff807`70520a39 4533c9 xor r9d,r9d
fffff807`70520a3c 4533c0 xor r8d,r8d
fffff807`70520a3f e80c000000 call nt!KiSetTimerEx (fffff807`70520a50)
fffff807`70520a44 4883c438 add rsp,38h
fffff807`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.com \n"); ULONG64 address = GetKeSetTimerEx();
if (address != 0)
{
DbgPrint("KeSetTimerEx = %p \n", address);
} Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

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

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

  • 488b05850c5100 KiWaitNever
  • 488b356b0e5100 KiWaitAlways

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

/*
0: kd> uf KiSetTimerEx
nt!KiSetTimerEx:
fffff807`70520a50 48895c2408 mov qword ptr [rsp+8],rbx
fffff807`70520a55 48896c2410 mov qword ptr [rsp+10h],rbp
fffff807`70520a5a 4889742418 mov qword ptr [rsp+18h],rsi
fffff807`70520a5f 57 push rdi
fffff807`70520a60 4154 push r12
fffff807`70520a62 4155 push r13
fffff807`70520a64 4156 push r14
fffff807`70520a66 4157 push r15
fffff807`70520a68 4883ec50 sub rsp,50h
fffff807`70520a6c 488b05850c5100 mov rax,qword ptr [nt!KiWaitNever (fffff807`70a316f8)]
fffff807`70520a73 488bf9 mov rdi,rcx
fffff807`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 字节找 找 KiWaitNever
for (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 字节找 找 KiWaitNever
for (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中找到的地址一致。

功能实现部分: 最后将这些功能整合在一起,循环输出链表元素,并解密元素即可实现枚举当前系统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!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 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 l20
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
*/
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 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)]
*/
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 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;
} 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++)
{
// 线程绑定特定 CPU
KeSetSystemAffinityThread(i + 1); // 获得 KPRCB 的地址
ULONG64 p_PRCB = (ULONG64)__readmsr(0xC0000101) + 0x20;
if (!MmIsAddressValid((PVOID64)p_PRCB))
{
return FALSE;
} // 取消绑定 CPU
KeRevertToUserAffinityThread(); // 判断操作系统版本
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 和 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("[LyShark] 定时器对象:0x%p | 函数入口:0x%p | 触发周期: %d \n ", p_Timer, p_Dpc->DeferredRoutine);
}
}
}
return STATUS_SUCCESS;
}

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

驱动开发:内核枚举DpcTimer定时器的更多相关文章

  1. 驱动开发:内核枚举IoTimer定时器

    今天继续分享内核枚举系列知识,这次我们来学习如何通过代码的方式枚举内核IoTimer定时器,内核定时器其实就是在内核中实现的时钟,该定时器的枚举非常简单,因为在IoInitializeTimer初始化 ...

  2. Windows驱动开发-内核常用内存函数

    搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool

  3. 驱动开发:内核枚举PspCidTable句柄表

    在上一篇文章<驱动开发:内核枚举DpcTimer定时器>中我们通过枚举特征码的方式找到了DPC定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows ...

  4. 驱动开发:Win10内核枚举SSDT表基址

    三年前面朝黄土背朝天的我,写了一篇如何在Windows 7系统下枚举内核SSDT表的文章<驱动开发:内核读取SSDT表基址>三年过去了我还是个单身狗,开个玩笑,微软的Windows 10系 ...

  5. 《Windows内核安全与驱动开发》4.3 时间与定时器

    <Windows内核安全与驱动开发>阅读笔记 -- 索引目录 <Windows内核安全与驱动开发>4.3  时间与定时器 一.获取自系统启动以来的毫秒数 /* 函数作用:求自操 ...

  6. 驱动开发:内核枚举ShadowSSDT基址

    在笔者上一篇文章<驱动开发:Win10枚举完整SSDT地址表>实现了针对SSDT表的枚举功能,本章继续实现对SSSDT表的枚举,ShadowSSDT中文名影子系统服务描述表,SSSDT其主 ...

  7. 驱动开发:内核枚举LoadImage映像回调

    在笔者之前的文章<驱动开发:内核特征码搜索函数封装>中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核LoadImage映像回调,在Win64环境下我们可以设置一个 ...

  8. 驱动开发:内核枚举Registry注册表回调

    在笔者上一篇文章<驱动开发:内核枚举LoadImage映像回调>中LyShark教大家实现了枚举系统回调中的LoadImage通知消息,本章将实现对Registry注册表通知消息的枚举,与 ...

  9. Windows内核安全与驱动开发

    这篇是计算机中Windows Mobile/Symbian类的优质预售推荐<Windows内核安全与驱动开发>. 编辑推荐 本书适合计算机安全软件从业人员.计算机相关专业院校学生以及有一定 ...

随机推荐

  1. cmd命令行工具

    在windows下进行python开发,需要经常使用cmd命令行工具.打开命令行工具有很多种方法,最简单的就是win键+R键弹出运行窗口,然后输入cmd, 就会打开下面这样的窗口. 不同版本,可能配色 ...

  2. 深入理解Aarch64内存管理

    本文是对learn_the_architecture_-_aarch64_memory_management的部分翻译和个人注解.个人英文水平有限,若有翻译不当,欢迎加我私人微信LinuxDriver ...

  3. 修改后台传过来的json数据中对象的属性

    前言 今天在实习中遇到的一个小问题,后端传过来的一个json数据结构,但是对象中的属性名跟我需要的不一样(因为我是渲染echarts中的数据,属性名要一样) 这是后台传过来的数据: 需求是我需要把属性 ...

  4. Redis 15 主从复制

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 概述 主从复制 ...

  5. identity4 系列————启航篇[二]

    前言 开始identity的介绍了. 正文 前文介绍了一些概念,如果概念不清的话,可以去前文查看. https://www.cnblogs.com/aoximin/p/13475444.html 对一 ...

  6. java-注释、API之字符串(String)

    /** * 文档注释只定义在三个地方 : 类.常量.方法上 * 在类上定义文档注释用来说这个类设计及其解决问题等相关描述信息 * @author 作者 * @version  1.0 21/08/17 ...

  7. Redis的两种持久化机制

    Redis的两种持久化机制 1.持久化机制 client--->redis(内存)--->内存数据-数据持久化--->磁盘 两种方法 快照(Snapshot) AOF(Append ...

  8. 分布式id生成器,雪花算法IdWorker

    /** * <p>名称:IdWorker.java</p> * <p>描述:分布式自增长ID</p> * <pre> * Twitter的 ...

  9. 系统CPU飙高,怎么排查?

    cpu是整个电脑的核心计算资源,对于一个应用进程来说,cpu的最小执行单元是线程. 导致cpu飙高的原因有几个方面: cpu上下文切换过多,对于cpu来说,同一时刻下每个cpu核心只能运行一个线程,如 ...

  10. CF600E Lomsat gelral (dfs序+莫队)

    题面 题解 看到网上写了很多DSU和线段树合并的题解,笔者第一次做也是用的线段树合并,但在原题赛的时候却怕线段树合并调不出来,于是就用了更好想更好调的莫队. 这里笔者就说说莫队怎么做吧. 我们可以通过 ...