枚举IO_TIMER计时器

时间:2022-03-01 20:36:12

前提:

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

偏移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

以上两结构体通过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
);

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


可以看出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

我们在Windbg上用DD XXXXXXXX 顺藤摸瓜就可以拿到所有的 *LIST_ENTRY
枚举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

得到 LIST_ENTRY指针后,得到回调函数就是+8的事了,不是吗?
若有怀疑,可以对照着PCHunter 来看看


图解一下上图: 我们可以在IO_TIMER链表头偏移的前4byte处拿到 下一个IO_TIMER节点
TimerList的地址 :0X8949253C - 4 就得到 IO_TIMER 结构体的地址0X89492538
对比下图,看看存在不存在 0X89492538
枚举IO_TIMER计时器

正文: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;
}