前提:
1。首先你得了解什么是IO_TIMER计时器
不了解的话可以去看一下《Windows驱动开发详解》第10章
2。你得会简单驱动的编写
不了解的话还是可以看一下《windows驱动开发详解》,或是参照网上的教程,开发环境是拦路虎,搭好了,再跟着书上例子,慢慢来,以及多网上查阅
3。你得清楚我的实验环境是在32bit-XP SP3下,因此我这儿的情况可能并不适用你那儿,但是走走流程还是有必要的
正文:1【编程创建一个IO定时器】
【1】首先你得利用IoCreateDevice 创建一个设备对象
NTSTATUS IoCreateDevice( _In_ PDRIVER_OBJECT DriverObject, _In_ ULONG DeviceExtensionSize, _In_opt_ PUNICODE_STRING DeviceName, _In_ DEVICE_TYPE DeviceType, _In_ ULONG DeviceCharacteristics, _In_ BOOLEAN Exclusive, _Out_ PDEVICE_OBJECT *DeviceObject );
nt!_DEVICE_OBJECT
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 ReferenceCount : Int4B
+0x008 DriverObject : Ptr32 _DRIVER_OBJECT
+0x00c NextDevice : Ptr32 _DEVICE_OBJECT
+0x010 AttachedDevice : Ptr32 _DEVICE_OBJECT
+0x014 CurrentIrp : Ptr32 _IRP
+0x018 Timer : Ptr32 _IO_TIMER
+0x01c Flags : Uint4B
+0x020 Characteristics : Uint4B
+0x024 Vpb : Ptr32 _VPB
+0x028 DeviceExtension : Ptr32 Void
+0x02c DeviceType : Uint4B
+0x030 StackSize : Char
+0x034 Queue : __unnamed
+0x05c AlignmentRequirement : Uint4B
+0x060 DeviceQueue : _KDEVICE_QUEUE
+0x074 Dpc : _KDPC
+0x094 ActiveThreadCount : Uint4B
+0x098 SecurityDescriptor : Ptr32 Void
+0x09c DeviceLock : _KEVENT
+0x0ac SectorSize : Uint2B
+0x0ae Spare1 : Uint2B
+0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
+0x0b4 Reserved : Ptr32 Void
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 ReferenceCount : Int4B
+0x008 DriverObject : Ptr32 _DRIVER_OBJECT
+0x00c NextDevice : Ptr32 _DEVICE_OBJECT
+0x010 AttachedDevice : Ptr32 _DEVICE_OBJECT
+0x014 CurrentIrp : Ptr32 _IRP
+0x018 Timer : Ptr32 _IO_TIMER
+0x01c Flags : Uint4B
+0x020 Characteristics : Uint4B
+0x024 Vpb : Ptr32 _VPB
+0x028 DeviceExtension : Ptr32 Void
+0x02c DeviceType : Uint4B
+0x030 StackSize : Char
+0x034 Queue : __unnamed
+0x05c AlignmentRequirement : Uint4B
+0x060 DeviceQueue : _KDEVICE_QUEUE
+0x074 Dpc : _KDPC
+0x094 ActiveThreadCount : Uint4B
+0x098 SecurityDescriptor : Ptr32 Void
+0x09c DeviceLock : _KEVENT
+0x0ac SectorSize : Uint2B
+0x0ae Spare1 : Uint2B
+0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
+0x0b4 Reserved : Ptr32 Void
偏移0X18处的Timer 类型显示的是
Ptr32 _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
+0x000 Type : Int2B
+0x002 TimerFlag : Int2B
+0x004 TimerList : _LIST_ENTRY
+0x00c TimerRoutine : Ptr32 void
+0x010 Context : Ptr32 Void
+0x014 DeviceObject : Ptr32 _DEVICE_OBJECT
以上两结构体通过WinDbg获取,WinDbg调试的对象系统是 XP ,SP3
【2】调用IoInitializeTimer 函数
NTSTATUS IoInitializeTimer( _In_ PDEVICE_OBJECT DeviceObject, _In_ PIO_TIMER_ROUTINE TimerRoutine, _In_opt_ PVOID Context );
DeviceObject是刚才创建的设备对象,TimerRoutine是你 程序 定义的 一个 IO计时器回调函数,每1S 左右调用一次,通过可以通过自己的手段让,触发间隔变为N X 1 秒,
Context是调用你的回调函数时,传给你自定义的回调函数的 一个数据结构体的指针,你得保证函数调用时,该指针还有效,(如果你在函数内部使用了的话)
【3】调用IoStartTimer函数
VOID IoStartTimer( _In_ PDEVICE_OBJECT DeviceObject );
正文:2【综合信息】
设备对象中有个IO_TIMER 结构体的指针,IO_TIMER结构体有个为链表插入服务的 LIST_ENTRY, IoInitializeTimer函数,
将一个设备对象和一个函数关联,接着调用IoStartTimer,参数是设备对象 。
正文:3【分析】
用IDA 加载 NTOSKRNL.exe
,加载时如果在有网的环境下,会提示你,是否连接到 微软符号服务器上:选择是。。。
在导出本函数视图中输入
IoInitializeTimer,双击进入 反汇编视图
; NTSTATUS __stdcall IoInitializeTimer(PDEVICE_OBJECT DeviceObject, PIO_TIMER_ROUTINE TimerRoutine, PVOID Context)
PAGE:004E250F public _IoInitializeTimer@12
PAGE:004E250F _IoInitializeTimer@12 proc near ; CODE XREF: IovInitializeTimer(x,x,x)+25p
PAGE:004E250F ; DATA XREF: .edata:off_5AD0A8o
PAGE:004E250F
PAGE:004E250F DeviceObject = dword ptr 8
PAGE:004E250F TimerRoutine = dword ptr 0Ch
PAGE:004E250F Context = dword ptr 10h
PAGE:004E250F
PAGE:004E250F ; FUNCTION CHUNK AT PAGE:0050DA64 SIZE 0000000A BYTES
PAGE:004E250F
PAGE:004E250F mov edi, edi
PAGE:004E2511 push ebp
PAGE:004E2512 mov ebp, esp
PAGE:004E2514 push esi
PAGE:004E2515 mov esi, [ebp+DeviceObject] //esi 存放设备对象指针
PAGE:004E2518 mov edx, [esi+18h]//看看前文的结构体,edx存放 IO_TIMER 指针
PAGE:004E251B test edx, edx//判断IO_TIMER指针是否为0
PAGE:004E251D jnz short PIO_TIMER_NOT_0 //不为0,说明有了,跳转过去
PAGE:004E251F push 'iToI' ; Tag
PAGE:004E2524 push 18h ; NumberOfBytes
PAGE:004E2526 push edx ; PoolType
PAGE:004E2527 call _ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)
PAGE:004E252C mov edx, eax
PAGE:004E252E test edx, edx
PAGE:004E2530 jz POOL_ALLOCATION_FAILED//分配IO_TIMER结构体失败,跳转
PAGE:004E2536 push edi ; 分配内存给 IO_TIMER ,并初始化
PAGE:004E2537 push 6
PAGE:004E2539 pop ecx
PAGE:004E253A xor eax, eax
PAGE:004E253C mov edi, edx
PAGE:004E253E rep stosd//给创建的IO_TIMER结构体清0,接着初始化字段
PAGE:004E2540 mov word ptr [edx], 9//IO_TIMER.Type字段置9
PAGE:004E2545 mov [edx+14h], esi//IO_TIMER.DeviceObject字段存入 传入设备对象指针
PAGE:004E2548 mov [esi+18h], edx//分配的IO_TIMER的指针赋给DeviceObj的PIO_TIMER类型的字段
PAGE:004E254B pop edi
PAGE:004E254C
PAGE:004E254C PIO_TIMER_NOT_0: ; CODE XREF: IoInitializeTimer(x,x,x)+Ej
PAGE:004E254C mov eax, [ebp+TimerRoutine]//设置函数
PAGE:004E254F mov [edx+0Ch], eax
PAGE:004E2552 mov eax, [ebp+Context]
PAGE:004E2555 mov [edx+10h], eax
PAGE:004E2558 push offset _IopTimerLock ; Lock//传入参数
PAGE:004E255D add edx, 4 ; ListEntry//传入 IO_TIMER的LIST_ENTRY的地址
PAGE:004E2560 mov ecx, offset _IopTimerQueueHead ; ListHead
PAGE:004E2565 call @ExfInterlockedInsertTailList@12 ;// FAST CALL调用
PAGE:004E256A xor eax, eax
PAGE:004E256C
PAGE:004E256C loc_4E256C: ; CODE XREF: IoInitializeTimer(x,x,x)+2B55Aj
PAGE:004E256C pop esi
PAGE:004E256D pop ebp
PAGE:004E256E retn 0Ch
PAGE:004E256E _IoInitializeTimer@12 endp
PAGE:004E250F public _IoInitializeTimer@12
PAGE:004E250F _IoInitializeTimer@12 proc near ; CODE XREF: IovInitializeTimer(x,x,x)+25p
PAGE:004E250F ; DATA XREF: .edata:off_5AD0A8o
PAGE:004E250F
PAGE:004E250F DeviceObject = dword ptr 8
PAGE:004E250F TimerRoutine = dword ptr 0Ch
PAGE:004E250F Context = dword ptr 10h
PAGE:004E250F
PAGE:004E250F ; FUNCTION CHUNK AT PAGE:0050DA64 SIZE 0000000A BYTES
PAGE:004E250F
PAGE:004E250F mov edi, edi
PAGE:004E2511 push ebp
PAGE:004E2512 mov ebp, esp
PAGE:004E2514 push esi
PAGE:004E2515 mov esi, [ebp+DeviceObject] //esi 存放设备对象指针
PAGE:004E2518 mov edx, [esi+18h]//看看前文的结构体,edx存放 IO_TIMER 指针
PAGE:004E251B test edx, edx//判断IO_TIMER指针是否为0
PAGE:004E251D jnz short PIO_TIMER_NOT_0 //不为0,说明有了,跳转过去
PAGE:004E251F push 'iToI' ; Tag
PAGE:004E2524 push 18h ; NumberOfBytes
PAGE:004E2526 push edx ; PoolType
PAGE:004E2527 call _ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)
PAGE:004E252C mov edx, eax
PAGE:004E252E test edx, edx
PAGE:004E2530 jz POOL_ALLOCATION_FAILED//分配IO_TIMER结构体失败,跳转
PAGE:004E2536 push edi ; 分配内存给 IO_TIMER ,并初始化
PAGE:004E2537 push 6
PAGE:004E2539 pop ecx
PAGE:004E253A xor eax, eax
PAGE:004E253C mov edi, edx
PAGE:004E253E rep stosd//给创建的IO_TIMER结构体清0,接着初始化字段
PAGE:004E2540 mov word ptr [edx], 9//IO_TIMER.Type字段置9
PAGE:004E2545 mov [edx+14h], esi//IO_TIMER.DeviceObject字段存入 传入设备对象指针
PAGE:004E2548 mov [esi+18h], edx//分配的IO_TIMER的指针赋给DeviceObj的PIO_TIMER类型的字段
PAGE:004E254B pop edi
PAGE:004E254C
PAGE:004E254C PIO_TIMER_NOT_0: ; CODE XREF: IoInitializeTimer(x,x,x)+Ej
PAGE:004E254C mov eax, [ebp+TimerRoutine]//设置函数
PAGE:004E254F mov [edx+0Ch], eax
PAGE:004E2552 mov eax, [ebp+Context]
PAGE:004E2555 mov [edx+10h], eax
PAGE:004E2558 push offset _IopTimerLock ; Lock//传入参数
PAGE:004E255D add edx, 4 ; ListEntry//传入 IO_TIMER的LIST_ENTRY的地址
PAGE:004E2560 mov ecx, offset _IopTimerQueueHead ; ListHead
PAGE:004E2565 call @ExfInterlockedInsertTailList@12 ;// FAST CALL调用
PAGE:004E256A xor eax, eax
PAGE:004E256C
PAGE:004E256C loc_4E256C: ; CODE XREF: IoInitializeTimer(x,x,x)+2B55Aj
PAGE:004E256C pop esi
PAGE:004E256D pop ebp
PAGE:004E256E retn 0Ch
PAGE:004E256E _IoInitializeTimer@12 endp
可以看出IoInitializeTimer这个函数,为给定的设备对象,检查是否已经有了一个
IO_TIMER 对象,如果有的话,直接重新设置 回调函数,和回调函数参数,没有的话,
分配一个IO_TIMER对象,并用传入参数,初始化它,最后将 新创建的 IO_TIMER对象
的LIST_ENTRY结构体的地址,和一个锁,一个符号名为 _IopTimerQeueHead的地址
传入另一个函数中去处理,从函数名称,可以推断出该函数 完成插入操作的,
至此,我在我的XP SP3上发现了,IO计时器链表头了,仅仅是用硬编码的手段就可以得出 _IopTimerQeueHead的地址,如你截图所见
PAGE:004E2560 处的字节码 在我IDA 这儿显示的是 B9 60 1B 48 00,
在WinDbg下是 805ba560 b9609b5580 mov ecx,offset nt!IopTimerQueueHead (80559b60),
如果你熟悉PE结构的话,你就知道,B9后面的4BYTE 处这里必然会在文件载入内存中时发生重定位,故此 B9 后面4BYTE是什么 不重要,重要的是,我的驱动,一定在调用
IoInitializeTimer时可以得到,一个值,这个值代表的是 IO_TIMER 链表头 的偏移,
kd> dt _list_entry
nt!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
nt!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
我们在Windbg上用DD XXXXXXXX 顺藤摸瓜就可以拿到所有的 *LIST_ENTRY
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
+0x000 Type : Int2B
+0x002 TimerFlag : Int2B
+0x004 TimerList : _LIST_ENTRY
+0x00c TimerRoutine : Ptr32 void
+0x010 Context : Ptr32 Void
+0x014 DeviceObject : Ptr32 _DEVICE_OBJECT
得到 LIST_ENTRY指针后,得到回调函数就是+8的事了,不是吗?
若有怀疑,可以对照着PCHunter 来看看
图解一下上图: 我们可以在IO_TIMER链表头偏移的前4byte处拿到 下一个IO_TIMER节点
的TimerList的地址 :0X8949253C - 4 就得到 IO_TIMER 结构体的地址0X89492538
对比下图,看看存在不存在
0X89492538
正文:4【我的实现】
#include <ntddk.h> //下面是为枚举 IO_TIMER 所涉及到的结构体 typedef struct _IO_TIMER { SHORT Type; SHORT TimerFlag; LIST_ENTRY TimerList; PVOID TimerRoutine; PVOID Context; PDEVICE_OBJECT DeviceObject; } IO_TIMER, *PIO_TIMER; //下面的结构体是为枚举内核模块用的,可以用WinDbg 命令 dt _LDR_DATA_TABLE_ENTRY获取,前提是你得配置好符号路径 typedef struct _LDR_DATA_TABLE_ENTRY{ LIST_ENTRY InLoadOrderLinks; //+0x000 InLoadOrderLinks : _LIST_ENTRY LIST_ENTRY InMemoryOrderLinks; //+ 0x008 InMemoryOrderLinks : _LIST_ENTRY LIST_ENTRY InInitializationOrderLinks; //+ 0x010 InInitializationOrderLinks : _LIST_ENTRY ULONG DllBase; //+ 0x018 DllBase : Ptr32 Void ULONG EntryPoint; //+ 0x01c EntryPoint : Ptr32 Void ULONG SizeOfImage; //+ 0x020 SizeOfImage : Uint4B UNICODE_STRING FullDllName; //+ 0x024 FullDllName : _UNICODE_STRING UNICODE_STRING BaseDllName; //+ 0x02c BaseDllName : _UNICODE_STRING ULONG Flags; //+ 0x034 Flags : Uint4B USHORT LoadCount; //+ 0x038 LoadCount : Uint2B USHORT TlsIndex; //+ 0x03a TlsIndex : Uint2B LIST_ENTRY HashLinks; //+ 0x03c HashLinks : _LIST_ENTRY ULONG SectionPointer; //+ 0x03c SectionPointer : Ptr32 Void ULONG CheckSum; //+ 0x040 CheckSum : Uint4B ULONG TimeDateStamp; //+ 0x044 TimeDateStamp : Uint4B ULONG LoadedImports; //+ 0x044 LoadedImports : Ptr32 Void ULONG EntryPointActivationContext;//+ 0x048 EntryPointActivationContext : Ptr32 Void ULONG PatchInformation; //+ 0x04c PatchInformation : Ptr32 Void }LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; VOID DriverUnload(PDRIVER_OBJECT pDriverObj){ return; } //获取IoInitializeTimer函数地址 ULONG GetIoInitializeTimerAddr(){ UNICODE_STRING uStrFunc = { 0 }; RtlInitUnicodeString(&uStrFunc, L"IoInitializeTimer"); return (ULONG)MmGetSystemRoutineAddress(&uStrFunc); } //给定一个地址,从本驱动模块开始搜索,判断输出所在模块的PUNICODE_STRING形式的名称 PUNICODE_STRING FindKernelModule(PDRIVER_OBJECT pDriverObj, ULONG funcAddr){ PLDR_DATA_TABLE_ENTRY p = (PLDR_DATA_TABLE_ENTRY)pDriverObj->DriverSection; PLIST_ENTRY pList, pStartList; pList = pStartList = (PLIST_ENTRY)p; do{ p = (PLDR_DATA_TABLE_ENTRY)pList; if (p->DllBase != 0){ if (p->DllBase <= funcAddr && (p->DllBase + p->SizeOfImage) > funcAddr){ return &p->BaseDllName; } } pList = pList->Flink; } while (pList != pStartList); return NULL; } VOID EnumerateIOTimer(PDRIVER_OBJECT pDriverObj){ ULONG pFunc = GetIoInitializeTimerAddr(); if (pFunc != 0){ KdPrint(("Getting Func address successfaully :0X%08X", pFunc)); RTL_OSVERSIONINFOW os = { sizeof(os) }; RtlGetVersion(&os); if (os.dwMajorVersion == 5 && os.dwMinorVersion == 1){ //我这里只是在XP下 所得的情况,直接使用了硬编码的形式,故而不具有通用性,所以仅供参考 ULONG listHead = *(ULONG*)(pFunc + 0x52); PLIST_ENTRY listHeader = (PLIST_ENTRY)listHead; PLIST_ENTRY tempList = listHeader; tempList = listHeader->Flink; while (tempList!=listHeader){ static ULONG index = 1; PIO_TIMER pIOTimer = (PIO_TIMER)((ULONG)tempList - 4) ; USHORT type = pIOTimer->Type; USHORT flag = pIOTimer->TimerFlag; PVOID device = pIOTimer->DeviceObject; PVOID context = pIOTimer->Context; PVOID routine = pIOTimer->TimerRoutine; //不用unicode_string DebugView和Windbg 会在输出中文UnicodeString时截断 ANSI_STRING as = { 0 }; RtlUnicodeStringToAnsiString(&as, FindKernelModule(pDriverObj, (ULONG)routine), TRUE); KdPrint(("[IoTimer %d]->0X%08X :type-0X%04X flag-0X%04X device-0X%08X context-0X%08X routine-0X%08X module-%Z \n" , index, (ULONG)pIOTimer, type, flag, device, context, routine, &as )); RtlFreeAnsiString(&as); tempList = tempList->Flink; index++; } } } } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegPath){ pDriverObj->DriverUnload=DriverUnload; EnumerateIOTimer(pDriverObj); return STATUS_SUCCESS; }