Windows内核定时器
常用的内核定时器有两种:IoInitializeTimer、IoStartTimer和IoStopTimer三个函数的IO定时器;KeInitializeTimer、KeSetTimer和KeCancelTimer三个函数的DPC定时器。前一套更简单,后一套功能更强大一些。
IO定时器
IO定时器每隔1s触发一次自己定义的IO例程,可以用来做一些需要特定频率的事情。Windbg下看一下IO定时器三个函数的实现,比较简单。看一下函数原型和必要的结构。
lkd> dt _io_timer
nt!_IO_TIMER
+0x000 Type : Int2B
+0x002 TimerFlag : Int2B
+0x004 TimerList : _LIST_ENTRY
+0x00c TimerRoutine : Ptr32 void
+0x010 Context : Ptr32 Void
+0x014 DeviceObject : Ptr32 _DEVICE_OBJECT
NTSTATUS IoInitializeTimer(
IN PDEVICE_OBJECT DeviceObject,
IN PIO_TIMER_ROUTINE TimerRoutine,
IN PVOID Context
);
lkd> uf IoInitializeTimer
nt!IoInitializeTimer:
8056a3da 8bff mov edi,edi
8056a3dc 55 push ebp
8056a3dd 8bec mov ebp,esp
8056a3df 56 push esi
8056a3e0 8b7508 mov esi,dword ptr [ebp+8]
// 第一个参数,设备对象
8056a3e3 8b5618 mov edx,dword ptr [esi+18h]
// DEVICE_OBJECT偏移18h处是 +0x018 Timer : Ptr32 _IO_TIMER
8056a3e6 85d2 test edx,edx
8056a3e8 7530 jne nt!IoInitializeTimer+0x40 (8056a41a)
// 这里说明每个对象只能有一个IO定时器
nt!IoInitializeTimer+0x10:
8056a3ea 68496f5469 push 69546F49h
8056a3ef 6a18 push 18h
8056a3f1 52 push edx
8056a3f2 e889bbfdff call nt!ExAllocatePoolWithTag (80545f80)
8056a3f7 8bd0 mov edx,eax
8056a3f9 85d2 test edx,edx
8056a3fb 7507 jne nt!IoInitializeTimer+0x2a (8056a404)
// 分配内存并初始化定时器
nt!IoInitializeTimer+0x23:
8056a3fd b89a0000c0 mov eax,0C000009Ah
8056a402 eb36 jmp nt!IoInitializeTimer+0x60 (8056a43a)
nt!IoInitializeTimer+0x2a:
8056a404 57 push edi
8056a405 6a06 push 6
8056a407 59 pop ecx
8056a408 33c0 xor eax,eax
8056a40a 8bfa mov edi,edx
8056a40c f3ab rep stos dword ptr es:[edi]
8056a40e 66c7020900 mov word ptr [edx],9
8056a413 897214 mov dword ptr [edx+14h],esi
8056a416 895618 mov dword ptr [esi+18h],edx
8056a419 5f pop edi
// esi指向设备对象,这里对设备对象的Timer进行赋值同时进一步初始化Timer
nt!IoInitializeTimer+0x40:
8056a41a 8b450c mov eax,dword ptr [ebp+0Ch]
8056a41d 89420c mov dword ptr [edx+0Ch],eax
8056a420 8b4510 mov eax,dword ptr [ebp+10h]
8056a423 894210 mov dword ptr [edx+10h],eax
8056a426 6844be5480 push offset nt!IopTimerLock (8054be44)
8056a42b 83c204 add edx,4
8056a42e b960295580 mov ecx,offset nt!IopTimerQueueHead (80552960)
8056a433 e8d092fdff call nt!ExfInterlockedInsertTailList (80543708)
8056a438 33c0 xor eax,eax
// 将IO定时器上锁并加入队列,ExfInterlockedInsertTailList实现下面
nt!IoInitializeTimer+0x60:
8056a43a 5e pop esi
8056a43b 5d pop ebp
8056a43c c20c00 ret 0Ch
ExfInterlockedInsertTailList其实就是将Timer加入一个链表中,这里有一个全局变量IopTimerQueueHead,是链表头。
lkd> uf ExfInterlockedInsertTailList
nt!ExfInterlockedInsertTailList:
80543708 9c pushfd
80543709 fa cli
8054370a 8b4104 mov eax,dword ptr [ecx+4]
8054370d 890a mov dword ptr [edx],ecx
8054370f 894204 mov dword ptr [edx+4],eax
80543712 895104 mov dword ptr [ecx+4],edx
80543715 8910 mov dword ptr [eax],edx
80543717 9d popfd
80543718 33c1 xor eax,ecx
8054371a 7402 je nt!ExfInterlockedInsertTailList+0x16 (8054371e)
nt!ExfInterlockedInsertTailList+0x14:
8054371c 33c1 xor eax,ecx
nt!ExfInterlockedInsertTailList+0x16:
8054371e c20400 ret 4
可以看出定时器的创建很简单。下面看一下IoStartTimer和IoStopTimer。
VOID IoStartTimer(
IN PDEVICE_OBJECT DeviceObject
);
VOID IoStopTimer(
IN PDEVICE_OBJECT DeviceObject
);
lkd> uf IoStartTimer
nt!IoStartTimer:
804f0314 8bff mov edi,edi
804f0316 55 push ebp
804f0317 8bec mov ebp,esp
804f0319 8b4d08 mov ecx,dword ptr [ebp+8]
804f031c 8b4118 mov eax,dword ptr [ecx+18h]
// 获取设备的Timer成员
804f031f 8b89b0000000 mov ecx,dword ptr [ecx+0B0h]
// 设备对象B0h偏移处成员是DeviceObjectExtension设备扩展
804f0325 f641100f test byte ptr [ecx+10h],0Fh
804f0329 7515 jne nt!IoStartTimer+0x2c (804f0340)
nt!IoStartTimer+0x17:
804f032b fa cli
804f032c 6683780200 cmp word ptr [eax+2],0
804f0331 750c jne nt!IoStartTimer+0x2b (804f033f)
nt!IoStartTimer+0x1f:
804f0333 66c740020100 mov word ptr [eax+2],1
804f0339 ff05f4285580 inc dword ptr [nt!IopTimerCount (805528f4)]
// 设置Timer的TimerFlag
nt!IoStartTimer+0x2b:
804f033f fb sti
nt!IoStartTimer+0x2c:
804f0340 5d pop ebp
804f0341 c20400 ret 4
lkd> uf IoStopTimer
nt!IoStopTimer:
804f034a 8bff mov edi,edi
804f034c 55 push ebp
804f034d 8bec mov ebp,esp
804f034f 8b4508 mov eax,dword ptr [ebp+8]
804f0352 8b4018 mov eax,dword ptr [eax+18h]
804f0355 fa cli
804f0356 6683780200 cmp word ptr [eax+2],0
804f035b 740b je nt!IoStopTimer+0x1e (804f0368)
nt!IoStopTimer+0x13:
804f035d 6683600200 and word ptr [eax+2],0
804f0362 ff0df4285580 dec dword ptr [nt!IopTimerCount (805528f4)]
// 和IoStartTimer类似
nt!IoStopTimer+0x1e:
804f0368 fb sti
804f0369 5d pop ebp
804f036a c20400 ret 4
再给出一个样例代码:
#include <ntddk.h>
typedef struct _WORK
{
PIO_WORKITEM pIoWorkItem;
LONG nWorkNumber;
}WORK,*PWORK;
char g_fTimerStarted;
LONG g_nWorkToDo;
UNICODE_STRING g_usDeviceName;
UNICODE_STRING g_usSymbolicLinkName;
void WorkItemRoutine(PDEVICE_OBJECT pDeviceObject, PVOID pContext)
{
PWORK pWork;
pWork = (PWORK)pContext;
DbgPrint("WorkItem: Work #%d is done\r\n", pWork-> nWorkNumber);
IoFreeWorkItem(pWork-> pIoWorkItem);
ExFreePool(pWork);
}
void TimerRoutine(struct _DEVICE_OBJECT *pDeviceObject, PVOID pContext)
{
PWORK pWork;
PIO_WORKITEM IoWorkItem;
if ( g_nWorkToDo )
{
IoWorkItem = IoAllocateWorkItem(pDeviceObject);
if ( IoWorkItem )
{
pWork = ExAllocatePool(0, sizeof(WORK));
if ( pWork != NULL )
{
pWork-> pIoWorkItem = IoWorkItem;
pWork-> nWorkNumber = g_nWorkToDo;
IoQueueWorkItem(IoWorkItem, WorkItemRoutine, DelayedWorkQueue, pWork);
--g_nWorkToDo;
}
}
}
else
{
IoStopTimer(pDeviceObject);
g_fTimerStarted = FALSE;
}
}
void DriverUnload(PDRIVER_OBJECT DriverObject)
{
IoDeleteSymbolicLink(&g_usSymbolicLinkName);
if ( g_fTimerStarted )
{
IoStopTimer(DriverObject-> DeviceObject);
g_fTimerStarted = FALSE;
DbgPrint("WorkItem: Timer stopped\r\n");
}
IoDeleteDevice(DriverObject-> DeviceObject);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
PDEVICE_OBJECT DeviceObject;
RtlInitUnicodeString(&g_usDeviceName,L"\\Device\\IoTimer");
RtlInitUnicodeString(&g_usSymbolicLinkName, L"\\DosDevices\\IoTimer");
g_fTimerStarted = FALSE;
g_nWorkToDo = 5;
IoCreateDevice(DriverObject, 0, &g_usDeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &DeviceObject);
status = IoCreateSymbolicLink(&g_usSymbolicLinkName, &g_usDeviceName);
if ( status )
{
DbgPrint("WorkItem: Couldn't create device. Status: %08X\r\n", status);
IoDeleteDevice(DeviceObject);
}
else
{
DriverObject-> DriverUnload = (PDRIVER_UNLOAD)DriverUnload;
status = IoInitializeTimer(DeviceObject, TimerRoutine, 0);
if ( status != STATUS_SUCCESS)
{
DbgPrint("WorkItem: Couldn't initialize timer. \ Status: %08X\r\n", status);
IoDeleteSymbolicLink(&g_usSymbolicLinkName);
IoDeleteDevice(DeviceObject);
}
else
{
IoStartTimer(DeviceObject);
g_fTimerStarted = 1;
DbgPrint("WorkItem: Timer started\r\n");
status = 0;
}
}
return status;
}
DPC定时器
DPC定时器可以实现更精确的时间控制,时间粒度也可以更小,100ns为单位。看一下三个函数原型:
VOID KeInitializeTimerEx(
IN PKTIMER Timer,
IN TIMER_TYPE Type
);
typedef enum _TIMER_TYPE {
NotificationTimer,
SynchronizationTimer
} TIMER_TYPE;
typedef struct _KTIMER {
DISPATCHER_HEADER32 Header;
ULARGE_INTEGER DueTime;
LIST_ENTRY TimerListEntry;
struct _KDPC *Dpc;
LONG Period;
} KTIMER, *PKTIMER, *PRKTIMER;
BOOLEAN KeCancelTimer(
IN PKTIMER Timer
);
BOOLEAN KeSetTimerEx(
IN PKTIMER Timer,
IN LARGE_INTEGER DueTime,
IN LONG Period OPTIONAL,
IN PKDPC Dpc OPTIONAL
);
可以看到DPC定时器更偏向全局,KTIMER是分发器对象(有分发器头的都是分发器对象),可以用KeWaitForSingleObject等函数等待。KeSetTimerEx可以自己设置事件触发定时器,如果指定DPC,那么相应的DPC例程会被执行。
ThreadProc proc Param:DWORD
local dwCounter:DWORD
local pkThread:PVOID
local status:NTSTATUS
local kTimer:KTIMER
local liDueTime:LARGE_INTEGER
and dwCounter, 0
invoke DbgPrint, $CTA0("\nTimerWorks: Entering ThreadProc\n")
invoke KeGetCurrentIrql
invoke DbgPrint, $CTA0("TimerWorks: IRQL = %d\n"), eax
invoke KeGetCurrentThread
mov pkThread, eax
invoke KeQueryPriorityThread, eax
push eax
invoke DbgPrint, $CTA0("TimerWorks: Thread Priority = %d\n"), eax
pop eax
inc eax
inc eax
invoke KeSetPriorityThread, pkThread, eax
invoke KeQueryPriorityThread, pkThread
invoke DbgPrint, $CTA0("TimerWorks: Thread Priority = %d\n"), eax
invoke KeInitializeTimerEx, addr kTimer, SynchronizationTimer
or liDueTime.HighPart, -1
mov liDueTime.LowPart, -50000000
invoke KeSetTimerEx, addr kTimer, liDueTime.LowPart,\
liDueTime.HighPart, 1000, NULL
invoke DbgPrint, $CTA0("TimerWorks: Timer is set. \
It starts counting in 5 seconds...\n")
.while dwCounter < 10
invoke KeWaitForSingleObject, addr kTimer, Executive,\ KernelMode, FALSE, NULL
inc dwCounter
invoke DbgPrint, $CTA0("TimerWorks: Counter = %d\n"), dwCounter
.if g_fStop
invoke DbgPrint, $CTA0("TimerWorks: \ Stop counting to let the driver to be uloaded\n")
.break
.endif
.endw
invoke KeCancelTimer, addr kTimer
invoke DbgPrint, $CTA0("TimerWorks: Timer is canceled. Leaving ThreadProc\n")
invoke DbgPrint, $CTA0("TimerWorks: Our thread is about to terminate\n")
invoke PsTerminateSystemThread, STATUS_SUCCESS
ret
ThreadProc endp
StartThread proc
local status:NTSTATUS
local oa:OBJECT_ATTRIBUTES
local hThread:HANDLE
invoke DbgPrint, $CTA0("\nTimerWorks: Entering StartThread\n")
invoke PsCreateSystemThread, addr hThread, \
THREAD_ALL_ACCESS, NULL, NULL, NULL, ThreadProc, NULL
mov status, eax
.if eax == STATUS_SUCCESS
invoke ObReferenceObjectByHandle, hThread, \ THREAD_ALL_ACCESS, NULL, KernelMode, addr g_pkThread, NULL
invoke ZwClose, hThread
invoke DbgPrint, $CTA0("TimerWorks: Thread created\n")
.else
invoke DbgPrint, $CTA0("TimerWorks: Can't create Thread. Status: %08X\n"), eax
.endif
invoke DbgPrint, $CTA0("TimerWorks: Leaving StartThread\n")
mov eax, status
ret
StartThread endp