windows内核定时器,定时最小单位为100ns. 比ring3的定时器要精准得多,并且使用它,稳定性高,系统开销小,无需消息队列,且功能强大,是作为数据采集绝好的定时器。(
,有点像高钙片的广告词)
这个内核定时器,涉及到三个函数,分别是KeInitializeTimerEx,KeCancelTimer和KeSetTimerEx。下面我们使用windbg这个工具,来进一步看看这三个函数的功能。注:我用的环境是winxp sp2
一、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;
typedef struct _DISPATCHER_HEADER32 {
UCHAR Type;
UCHAR Absolute;
UCHAR Size;
UCHAR Inserted;
LONG SignalState;
LIST_ENTRY32 WaitListHead;
} DISPATCHER_HEADER32;
分析KeInitializeTimerEx函数如下:
lkd> u KeInitializeTimerEx l 50
nt!KeInitializeTimerEx:
804f9d00 8bff mov edi,edi
804f9d02 55 push ebp
804f9d03 8bec mov ebp,esp
804f9d05 8b4508 mov eax,dword ptr [ebp+8] ;取参数1,Timer
804f9d08 8a4d0c mov cl,byte ptr [ebp+0Ch] ;取参数2,Type
804f9d0b 80c108 add cl,8
804f9d0e 33d2 xor edx,edx
;设置Timer->Header.Type = 参数2 Type + 8
804f9d10 8808 mov byte ptr [eax],cl
;ecx = Timer->Header.WaitListHead
804f9d12 8d4808 lea ecx,[eax+8]
;设置Timer->Header.Inserted = 0
804f9d15 885003 mov byte ptr [eax+3],dl
;设置Timer->Header.Size = 10
804f9d18 c640020a mov byte ptr [eax+2],0Ah
;设置Timer->Header.SignalState= 0
804f9d1c 895004 mov dword ptr [eax+4],edx
;Timer->Header.WaitListHead.Blink = Timer->Header.WaitListHead;
804f9d1f 894904 mov dword ptr [ecx+4],ecx
;Timer->Header.WaitListHead.Flink = Timer->Header.WaitListHead;
804f9d22 8909 mov dword ptr [ecx],ecx
;Timer->DueTime.LowPart = 0
804f9d24 895010 mov dword ptr [eax+10h],edx
;Timer->DueTime.HighPart = 0
804f9d27 895014 mov dword ptr [eax+14h],edx
;Timer->DueTime.Period = 0
804f9d2a 895024 mov dword ptr [eax+24h],edx
804f9d2d 5d pop ebp
804f9d2e c20800 ret 8
逆向还原的代码如下:
VOID NTAPI KeInitializeTimerEx(OUT PKTIMER Timer,
IN TIMER_TYPE Type)
{
Timer->Header.Type = Type + 8;
Timer->Header.Inserted = 0;
Timer->Header.Size = 10;
Timer->Header.SignalState= 0;
Timer->Header.WaitListHead.Blink = Timer->Header.WaitListHead;
Timer->Header.WaitListHead.Flink = Timer->Header.WaitListHead;
Timer->DueTime.LowPart = 0;
Timer->DueTime.HighPart = 0;
Timer->Period = 0;
}
二、BOOLEAN KeCancelTimer(
IN PKTIMER Timer
);
分析KeCancelTimer函数如下:
lkd> u KeCancelTimer
nt!KeCancelTimer:
804f9d36 8bff mov edi,edi
804f9d38 55 push ebp
804f9d39 8bec mov ebp,esp
804f9d3b 53 push ebx
;提升IRQL = DISPATCH_LEVEL
804f9d3c ff1514874d80 call dword ptr [nt!_imp__KeRaiseIrqlToDpcLevel (804d8714)]
804f9d42 8b5508 mov edx,dword ptr [ebp+8] ;取参数,Timer
;if(Timer->Header.Inserted == 0)
goto 804f9d5d;
804f9d45 8a5a03 mov bl,byte ptr [edx+3]
804f9d48 84db test bl,bl
804f9d4a 7411 je nt!KeCancelTimer+0x27 (804f9d5d)
;Timer->Header.Inserted = 0;
804f9d4c c6420300 mov byte ptr [edx+3],0
;下面这几句,是从双向链表中删除当前Timer->Header.WaitListHead
804f9d50 56 push esi
;esi = Timer->Header.WaitListHead.Flink
804f9d51 8b7218 mov esi,dword ptr [edx+18h]
;edx = Timer->Header.WaitListHead.Blink
804f9d54 8b521c mov edx,dword ptr [edx+1Ch]
;Timer->Header.WaitListHead.Blink.Flink = Timer->Header.WaitListHead.Flink;
804f9d57 8932 mov dword ptr [edx],esi
;Timer->Header.WaitListHead.Blink.Blink = Timer->Header.WaitListHead.Blink;
804f9d59 895604 mov dword ptr [esi+4],edx
804f9d5c 5e pop esi
;al 保存原来的OldIrql
804f9d5d 8ac8 mov cl,al
804f9d5f e8647f0400 call nt!KiUnlockDispatcherDatabase (80541cc8)
804f9d64 8ac3 mov al,bl
804f9d66 5b pop ebx
804f9d67 5d pop ebp
804f9d68 c20400 ret 4
逆向后的代码如下:
BOOLEAN NTAPI KeCancelTimer(IN OUT PKTIMER Timer)
{
KIRQL OldIrql;
BOOLEAN Inserted;
OldIrql = KeRaiseIrqlToDpcLevel();
Inserted = Timer->Header.Inserted;
if (Inserted)
{
Timer->Header.Inserted = FALSE;
//从双向链表中删除当前Timer->Header.WaitListHead
Timer->Header.WaitListHead.Blink.Flink = Timer->Header.WaitListHead.Flink;
Timer->Header.WaitListHead.Blink.Blink = Timer->Header.WaitListHead.Blink;
}
//这个函数作用,后面分析。
KiReleaseDispatcherLock(OldIrql);
return Inserted;
}
继续看看KiUnlockDispatcherDatabase这个函数
lkd> u KiUnlockDispatcherDatabase l 50
nt!KiUnlockDispatcherDatabase:
;ds:[0FFDFF128h] 对应于 struct _KTHREAD *NextThread
80541cc8 833d28f1dfff00 cmp dword ptr ds:[0FFDFF128h],0
;if(NextThread)
goto 80541cf0 ;
else
goto 804d871c;
80541ccf 751f jne nt!KiUnlockDispatcherDatabase+0x28 (80541cf0)
80541cd1 ff251c874d80 jmp dword ptr [nt!_imp_KfLowerIrql (804d871c)]
80541cd7 833d94f9dfff00 cmp dword ptr ds:[0FFDFF994h],0
80541cde 75f1 jne nt!KiUnlockDispatcherDatabase+0x9 (80541cd1)
80541ce0 51 push ecx
;设置当前的IRQL = DISPATCH_LEVEL
80541ce1 b102 mov cl,2
80541ce3 ff1500874d80 call dword ptr [nt!_imp_HalRequestSoftwareInterrupt (804d8700)]
80541ce9 59 pop ecx
80541cea ff251c874d80 jmp dword ptr [nt!_imp_KfLowerIrql (804d871c)]
; cl 保存的是OldIrql,
; if(OldIrql >= DISPATCH_LEVEL)
goto 80541cd7;
80541cf0 80f902 cmp cl,2
80541cf3 7de2 jge nt!KiUnlockDispatcherDatabase+0xf (80541cd7)
;申请16字节空间
80541cf5 83ec10 sub esp,10h
;保存寄存器值
80541cf8 895c240c mov dword ptr [esp+0Ch],ebx
80541cfc 89742408 mov dword ptr [esp+8],esi
80541d00 897c2404 mov dword ptr [esp+4],edi
80541d04 892c24 mov dword ptr [esp],ebp
;ds:[0FFDFF01Ch] 对应于 struct _KPCR *SelfPcr
80541d07 8b1d1cf0dfff mov ebx,dword ptr ds:[0FFDFF01Ch]
;下面两句保存出当前的kthread节点和他的下一个节点
;+124 struct _KTHREAD *CurrentThread
;+128 struct _KTHREAD *NextThread
80541d0d 8bb328010000 mov esi,dword ptr [ebx+128h]
80541d13 8bbb24010000 mov edi,dword ptr [ebx+124h]
;下面两句是从链表中删除当前CurrentThread
; SelfPcr->NextThread = 0;
80541d19 c7832801000000000000 mov dword ptr [ebx+128h],0
;SelfPcr->CurrentThread = SelfPcr->NextThread;
80541d23 89b324010000 mov dword ptr [ebx+124h],esi
;(KTHREAD PTR[EDI]).WaitIrql = OldIrql;
80541d29 884f58 mov byte ptr [edi+58h],cl
; ecx = edi;
80541d2c 8bcf mov ecx,edi
;(KTHREAD PTR[EDI]).IdleSwapBlock = 1;
80541d2e c6475001 mov byte ptr [edi+50h],1
;下面几句执行线程切换
80541d32 e899f3fbff call nt!KiReadyThread (805010d0)
;cl = (KTHREAD PTR[EDI]).WaitIrql
80541d37 8a4f58 mov cl,byte ptr [edi+58h]
80541d3a e831010000 call nt!SwapContext (80541e70)
80541d3f 0ac0 or al,al
;由于前面设置SelfPcr->CurrentThread = SelfPcr->NextThread;
;因此在这里SelfPcr->CurrentThread指向的是esi
; if( SelfPcr->CurrentThread.WaitIrql != PASSIVE_LEVEL)
goto 80541d5e;
80541d41 8a4e58 mov cl,byte ptr [esi+58h]
80541d44 7518 jne nt!KiUnlockDispatcherDatabase+0x96 (80541d5e)
;恢复前面压栈的寄存器
80541d46 8b2c24 mov ebp,dword ptr [esp]
80541d49 8b7c2404 mov edi,dword ptr [esp+4]
80541d4d 8b742408 mov esi,dword ptr [esp+8]
80541d51 8b5c240c mov ebx,dword ptr [esp+0Ch]
80541d55 83c410 add esp,10h
80541d58 ff251c874d80 jmp dword ptr [nt!_imp_KfLowerIrql (804d871c)]
;设置当前的IRQL = APC_LEVEL
80541d5e b101 mov cl,1
80541d60 ff151c874d80 call dword ptr [nt!_imp_KfLowerIrql (804d871c)]
;APC派发
80541d66 33c0 xor eax,eax
80541d68 50 push eax ; 参数3
80541d69 50 push eax ;参数2
80541d6a 50 push eax ;参数1
80541d6b e8b8c4fbff call nt!KiDeliverApc (804fe228)
80541d70 33c9 xor ecx,ecx
80541d72 ebd2 jmp nt!KiUnlockDispatcherDatabase+0x7e (80541d46)
KiUnlockDispatcherDatabase 逆向代码如下:
#define KIP0PCRADDRESS 0xffdff000
#define K0IPCR ((ULONG_PTR)(KIP0PCRADDRESS))
#define PCR ((volatile KPCR * const)K0IPCR)
#define KeGetPcr() PCR
VOID FASTCALL KiUnlockDispatcherDatabase ( IN KIRQL OldIrql )
{
KIRQL CurIrql;
UCHAR IsApcPending;
PKTHREAD CurrentThread ,NextThread;
PKPCR SelfPcr ;
if( KeGetPcr()->NextThread)
{
if(OldIrql >= DISPATCH_LEVEL)
{
If(KeGetPcr()->RegisterArea[0x74] != 0)
{
KfLowerIrql(OldIrql );
}
else
{
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
KfLowerIrql(OldIrql );
}
}
else
{
SelfPcr = KeGetPcr()->SelfPcr;
NextThread = KeGetPcr()->NextThread;
//从链表中删除CurrentThread
CurrentThread = KeGetPcr()->CurrentThread;
KeGetPcr()->CurrentThread = KeGetPcr()->NextThread;
KeGetPcr()->NextThread = 0;
CurrentThread->WaitIrql = OldIrql;
CurrentThread->IdleSwapBlock = 1;
KiReadyThread(CurrentThread);
/*SwapContext 函数:
Arguments: http://www.bigbibi.cn wawa收集
;
; cl - APC interrupt bypass disable (zero enable, nonzero disable).
; edi - Address of previous thread.
; esi - Address of next thread.
; ebx - Address of PCR.
;
; Return value:
;
; al - Kernel APC pending.
; ebx - Address of PCR.
; esi - Address of current thread object.
*/
_asm
{
mov ebx,SelfPcr ;
mov esi,NextThread ;
mov edi,CurrentThread;
mov cl,OldIrql
SwapContext(); //呵呵,卡巴斯基detour了这个函数。
mov IsApcPending,al
mov CurrentThread,esi
}
CurIrql = CurCurrentThread->WaitIrql ;
if(IsApcPending)
{
CurIrql = APC_LEVEL;
KfLowerIrql(CurIrql );
KiDeliverApc(0,0,0);
CurIrql = 0;
KfLowerIrql(CurIrql );
}
else
{
KfLowerIrql(CurIrql );
}
}
}
else
{
KfLowerIrql(OldIrql );
}
}
三、BOOLEAN KeSetTimerEx(
IN PKTIMER Timer,
IN LARGE_INTEGER DueTime,
IN LONG Period OPTIONAL,
IN PKDPC Dpc OPTIONAL
);
分析KeSetTimerEx 函数如下:
lkd> u KeSetTimerEx l 50
nt!KeSetTimerEx:
804f9d70 8bff mov edi,edi
804f9d72 55 push ebp
804f9d73 8bec mov ebp,esp
804f9d75 51 push ecx
804f9d76 53 push ebx
804f9d77 56 push esi
804f9d78 57 push edi
;提升IRQL = DISPATCH_LEVEL
804f9d79 ff1514874d80 call dword ptr [nt!_imp__KeRaiseIrqlToDpcLevel (804d8714)]
804f9d7f 8b7508 mov esi,dword ptr [ebp+8] ;取参数1,Timer
804f9d82 8845ff mov byte ptr [ebp-1],al ;保存OldIrql
;if(Timer->Header.Inserted == 0)
; goto 804f9d9e;
804f9d85 8a4603 mov al,byte ptr [esi+3]
804f9d88 84c0 test al,al
804f9d8a 88450b mov byte ptr [ebp+0Bh],al ; 暂存Timer->Header.Inserted
804f9d8d 740f je nt!KeSetTimerEx+0x2e (804f9d9e)
;Timer->Header.Inserted == 0
804f9d8f c6460300 mov byte ptr [esi+3],0
;下面4句将当前的Timer->TimerListEntry从链表中删除
;eax = Timer->TimerListEntry.Flink;
804f9d93 8b4618 mov eax,dword ptr [esi+18h]
;ecx = Timer->TimerListEntry.Blink;
804f9d96 8b4e1c mov ecx,dword ptr [esi+1Ch]
;Timer->TimerListEntry.Blink.Flink = Timer->TimerListEntry.Flink;
804f9d99 8901 mov dword ptr [ecx],eax
;Timer->TimerListEntry.Blink.Blink = Timer->TimerListEntry.Blink;
804f9d9b 894804 mov dword ptr [eax+4],ecx
;Timer->Header.Inserted= 0,前面跳到这里
;参数DueTime.HighPart压栈,作为函数KiInsertTreeTimer的参数 3
804f9d9e ff7510 push dword ptr [ebp+10h]
804f9da1 8b7d18 mov edi,dword ptr [ebp+18h];取参数Dpc
804f9da4 8b5d14 mov ebx,dword ptr [ebp+14h];取参数Period
;参数DueTime.LowPart压栈,作为函数KiInsertTreeTimer的参数 2
804f9da7 ff750c push dword ptr [ebp+0Ch]
;Timer->Header.SignalState== 0
804f9daa 83660400 and dword ptr [esi+4],0
;ecx = Timer;
804f9dae 8bce mov ecx,esi ;KiInsertTreeTimer参数1
;Timer->Dpc = Dpc;
804f9db0 897e20 mov dword ptr [esi+20h],edi
;Timer->Period = Period;
804f9db3 895e24 mov dword ptr [esi+24h],ebx
;见后面分析
804f9db6 e85f690000 call nt!KiInsertTreeTimer (8050071a)
804f9dbb 85c0 test eax,eax
804f9dbd 755c jne nt!KeSetTimerEx+0xab (804f9e1b) //成功跳转
;eax = Timer->Header.WaitListHead
804f9dbf 8d4608 lea eax,[esi+8]
;比较Timer->Header.WaitListHead.Flink和Timer->Header.WaitListHead
804f9dc2 3900 cmp dword ptr [eax],eax
;相等跳转
804f9dc4 7409 je nt!KeSetTimerEx+0x5f (804f9dcf)
804f9dc6 33d2 xor edx,edx ;KiWaitTest 参数2
804f9dc8 8bce mov ecx,esi ;KiWaitTest 参数1
804f9dca e8b5780000 call nt!KiWaitTest (80501684) ;后面分析
;跳到这里 ,判断参数Dpc是否为NULL,如果为空则跳转
804f9dcf 85ff test edi,edi
804f9dd1 7421 je nt!KeSetTimerEx+0x84 (804f9df4)
804f9dd3 eb02 jmp nt!KeSetTimerEx+0x67 (804f9dd7)
804f9dd5 f390 pause
804f9dd7 a11800dfff mov eax,dword ptr ds:[FFDF0018h]
804f9ddc 8b0d1400dfff mov ecx,dword ptr ds:[0FFDF0014h]
804f9de2 3b051c00dfff cmp eax,dword ptr ds:[0FFDF001Ch]
804f9de8 75eb jne nt!KeSetTimerEx+0x65 (804f9dd5)
804f9dea 50 push eax
804f9deb 51 push ecx
804f9dec ff7620 push dword ptr [esi+20h] ;Timer->Dpc
;这里说明了<开发人员在使用 Windows NT 设备驱动程序时应当避免的事项列表中18条>
;18. 一定不要将相同的 DPC 指针传递给 KeSetTimer,或者 KeSetTimerEx 和 KeInsertQueueDpc ,因为这将导致竞争。原因就在这里。
804f9def e8f0100000 call nt!KeInsertQueueDpc (804faee4)
;参数Dpc = 0 跳到这里,判断参数Period是否为0,等于0则跳
804f9df4 85db test ebx,ebx
804f9df6 7423 je nt!KeSetTimerEx+0xab (804f9e1b)
;eax =Timer->Period
804f9df8 8b4624 mov eax,dword ptr [esi+24h]
804f9dfb 6aff push 0FFFFFFFFh ;参数4
804f9dfd 99 cdq
804f9dfe 68f0d8ffff push 0FFFFD8F0h ;参数3, = -10000
804f9e03 52 push edx ;参数2,EDX = Timer->Period的符号位
804f9e04 50 push eax ;参数1
804f9e05 e8b6c10300 call nt!_allmul (80535fc0) ;计算period到期时间
804f9e0a 8bf8 mov edi,eax
804f9e0c 8bda mov ebx,edx
804f9e0e 53 push ebx ; 参数3
804f9e0f 57 push edi ;参数2
804f9e10 8bce mov ecx,esi ;参数1
804f9e12 e803690000 call nt!KiInsertTreeTimer (8050071a) ;调用,后面分析
804f9e17 85c0 test eax,eax
804f9e19 74f3 je nt!KeSetTimerEx+0x9e (804f9e0e);调用不成功,循环
;参数Period = 0跳到这里,恢复原来的IRQL
804f9e1b 8a4dff mov cl,byte ptr [ebp-1]
804f9e1e e8a57e0400 call nt!KiUnlockDispatcherDatabase (80541cc8)
;取出暂存的Timer->Header.Inserted ,作为返回值
804f9e23 8a450b mov al,byte ptr [ebp+0Bh]
804f9e26 5f pop edi
804f9e27 5e pop esi
804f9e28 5b pop ebx
804f9e29 c9 leave
804f9e2a c21400 ret 14h
逆向还原的代码如下:
BOOLEAN NTAPI
KeSetTimerEx(IN OUT PKTIMER Timer,
IN LARGE_INTEGER DueTime,
IN LONG Period,
IN PKDPC Dpc OPTIONAL)
{
KIRQL OldIrql;
BOOLEAN Inserted;
OldIrql = KeRaiseIrqlToDpcLevel ();
Inserted = Timer->Header.Inserted;
if (Inserted)
{
Timer->Header.Inserted = FALSE;
// 从链表中删除节点
Timer->TimerListEntry.Blink.Flink = Timer->TimerListEntry.Flink;
Timer->TimerListEntry.Blink.Blink=Timer->TimerListEntry.Blink;
}
Timer->Dpc = Dpc;
Timer->Period = Period;
Timer->Header.SignalState = FALSE;
if (!KiInsertTreeTimer (Timer, DueTime))
{
//KiInsertTreeTimer 调用失败的情况
if(Timer->Header.WaitListHead.Flink != Timer->Header.WaitListHead)
{
//唤醒
KiWaitTest(Timer, IO_NO_INCREMENT);
}
/* 如果存在DPC */
if (Dpc)
{
while(KeGetPcr()->Self != KeGetPcr()->SelfPcr )
{
_asm pause;
}
/*插入 DPC */
KeInsertQueueDpc(Timer->Dpc,
KeGetPcr()->ArbitraryUserPointer ,
KeGetPcr()->SelfPcr );
}
/* 如果存在 period */
if (Timer->Period)
{
/* 重新插入Timer */
DueTime.QuadPart=Timer->Period * (-10000);
while (!KiInsertTreeTimer (Timer, DueTime));
}
}
KiUnlockDispatcherDatabase (OldIrql);
return Inserted;
}
到这里,这三个函数的用法就分析完了,后面附上一个内核定时器的例子。