今天继续分享内核枚举系列知识,这次我们来学习如何通过代码的方式枚举内核IoTimer
定时器,内核定时器其实就是在内核中实现的时钟,该定时器的枚举非常简单,因为在IoInitializeTimer
初始化部分就可以找到IopTimerQueueHead
地址,该变量内存储的就是定时器的链表头部。枚举IO定时器的案例并不多见,即便有也是无法使用过时的,此教程学到肯定就是赚到了。
枚举Io定时器过程是这样的:
IoInitializeTimer
函数,该函数可以通过MmGetSystemRoutineAddress
得到。0xFF
偏移量,并搜索特征定位到IopTimerQueueHead
链表头。IO_TIMER
结构体,并循环链表头输出。这里解释一下为什么要找IoInitializeTimer
这个函数他是一个初始化函数,既然是初始化里面一定会涉及到链表的存储问题,找到他就能找到定时器链表基址,该函数的定义如下。
1
2
3
4
5
6
|
NTSTATUS
IoInitializeTimer(
IN PDEVICE_OBJECT DeviceObject,
/
/
设备对象指针
IN PIO_TIMER_ROUTINE TimerRoutine,
/
/
定时器例程
IN PVOID Context
/
/
传给定时器例程的函数
);
|
接着我们需要得到IO定时器的结构定义,在DEVICE_OBJECT
设备对象指针中存在一个Timer
属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
lyshark.com: kd> dt _DEVICE_OBJECT
ntdll!_DEVICE_OBJECT
+
0x000
Type
: Int2B
+
0x002
Size : Uint2B
+
0x004
ReferenceCount : Int4B
+
0x008
DriverObject : Ptr64 _DRIVER_OBJECT
+
0x010
NextDevice : Ptr64 _DEVICE_OBJECT
+
0x018
AttachedDevice : Ptr64 _DEVICE_OBJECT
+
0x020
CurrentIrp : Ptr64 _IRP
+
0x028
Timer : Ptr64 _IO_TIMER
+
0x030
Flags : Uint4B
+
0x034
Characteristics : Uint4B
+
0x038
Vpb : Ptr64 _VPB
+
0x040
DeviceExtension : Ptr64 Void
+
0x048
DeviceType : Uint4B
+
0x04c
StackSize : Char
+
0x050
Queue : <anonymous
-
tag>
+
0x098
AlignmentRequirement : Uint4B
+
0x0a0
DeviceQueue : _KDEVICE_QUEUE
+
0x0c8
Dpc : _KDPC
+
0x108
ActiveThreadCount : Uint4B
+
0x110
SecurityDescriptor : Ptr64 Void
+
0x118
DeviceLock : _KEVENT
+
0x130
SectorSize : Uint2B
+
0x132
Spare1 : Uint2B
+
0x138
DeviceObjectExtension : Ptr64 _DEVOBJ_EXTENSION
+
0x140
Reserved : Ptr64 Void
|
这里的这个+0x028 Timer
定时器是一个结构体_IO_TIMER
其就是IO定时器的所需结构体。
1
2
3
4
5
6
7
8
|
lyshark.com: kd> dt _IO_TIMER
ntdll!_IO_TIMER
+
0x000
Type
: Int2B
+
0x002
TimerFlag : Int2B
+
0x008
TimerList : _LIST_ENTRY
+
0x018
TimerRoutine : Ptr64 void
+
0x020
Context : Ptr64 Void
+
0x028
DeviceObject : Ptr64 _DEVICE_OBJECT
|
如上方的基础知识有了也就够了,接着就是实际开发部分,首先我们需要编写一个GetIoInitializeTimerAddress()
函数,让该函数可以定位到IoInitializeTimer
所在内核中的基地址上面,具体实现调用代码如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
#include <ntifs.h>
/
/
得到IoInitializeTimer基址
/
/
By: LyShark 内核开发系列教程
PVOID GetIoInitializeTimerAddress()
{
PVOID VariableAddress
=
0
;
UNICODE_STRING uioiTime
=
{
0
};
RtlInitUnicodeString(&uioiTime, L
"IoInitializeTimer"
);
VariableAddress
=
(PVOID)MmGetSystemRoutineAddress(&uioiTime);
if
(VariableAddress !
=
0
)
{
return
VariableAddress;
}
return
0
;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint((
"Uninstall Driver Is OK \n"
));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint((
"hello lyshark.com \n"
));
/
/
得到基址
PUCHAR IoInitializeTimer
=
GetIoInitializeTimerAddress();
DbgPrint(
"IoInitializeTimer Address = %p \n"
, IoInitializeTimer);
Driver
-
>DriverUnload
=
UnDriver;
return
STATUS_SUCCESS;
}
|
运行这个驱动程序,然后对比下是否一致:
接着我们在反汇编代码中寻找IoTimerQueueHead
,此处在LyShark系统内这个偏移位置是nt!IoInitializeTimer+0x5d
具体输出位置如下。
1
2
3
4
5
6
7
8
9
10
|
lyshark.com: kd> uf IoInitializeTimer
nt!IoInitializeTimer
+
0x5d
:
fffff805`
74b85bed
488d5008
lea rdx,[rax
+
8
]
fffff805`
74b85bf1
48897018
mov qword ptr [rax
+
18h
],rsi
fffff805`
74b85bf5
4c8d054475e0ff
lea r8,[nt!IopTimerLock (fffff805`
7498d140
)]
fffff805`
74b85bfc
48897820
mov qword ptr [rax
+
20h
],rdi
fffff805`
74b85c00
488d0dd9ddcdff
lea rcx,[nt!IopTimerQueueHead (fffff805`
748639e0
)]
fffff805`
74b85c07
e8141e98ff call nt!ExInterlockedInsertTailList (fffff805`
74507a20
)
fffff805`
74b85c0c
33c0
xor eax,eax
|
在WinDBG中标注出颜色lea rcx,[nt!IopTimerQueueHead (fffff805
748639e0)]`更容易看到。
接着就是通过代码实现对此处的定位,定位我们就采用特征码搜索的方式,如下代码是特征搜索部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
#include <ntifs.h>
/
/
得到IoInitializeTimer基址
/
/
By: LyShark 内核开发系列教程
PVOID GetIoInitializeTimerAddress()
{
PVOID VariableAddress
=
0
;
UNICODE_STRING uioiTime
=
{
0
};
RtlInitUnicodeString(&uioiTime, L
"IoInitializeTimer"
);
VariableAddress
=
(PVOID)MmGetSystemRoutineAddress(&uioiTime);
if
(VariableAddress !
=
0
)
{
return
VariableAddress;
}
return
0
;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint((
"Uninstall Driver Is OK \n"
));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint((
"hello lyshark.com \n"
));
/
/
得到基址
PUCHAR IoInitializeTimer
=
GetIoInitializeTimerAddress();
DbgPrint(
"IoInitializeTimer Address = %p \n"
, IoInitializeTimer);
INT32 iOffset
=
0
;
PLIST_ENTRY IoTimerQueueHead
=
NULL;
PUCHAR StartSearchAddress
=
IoInitializeTimer;
PUCHAR EndSearchAddress
=
IoInitializeTimer
+
0xFF
;
UCHAR v1
=
0
, v2
=
0
, v3
=
0
;
for
(PUCHAR i
=
StartSearchAddress; i < EndSearchAddress; i
+
+
)
{
if
(MmIsAddressValid(i) && MmIsAddressValid(i
+
1
) && MmIsAddressValid(i
+
2
))
{
v1
=
*
i;
v2
=
*
(i
+
1
);
v3
=
*
(i
+
2
);
/
/
三个特征码
if
(v1
=
=
0x48
&& v2
=
=
0x8d
&& v3
=
=
0x0d
)
{
memcpy(&iOffset, i
+
3
,
4
);
IoTimerQueueHead
=
(PLIST_ENTRY)(iOffset
+
(ULONG64)i
+
7
);
DbgPrint(
"IoTimerQueueHead = %p \n"
, IoTimerQueueHead);
break
;
}
}
}
Driver
-
>DriverUnload
=
UnDriver;
return
STATUS_SUCCESS;
}
|
搜索三个特征码v1 == 0x48 && v2 == 0x8d && v3 == 0x0d
从而得到内存位置,运行驱动对比下。
运行代码会取出lea
指令后面的操作数,而不是取出lea指令的内存地址。
最后一步就是枚举部分,我们需要前面提到的IO_TIMER
结构体定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
/
/
By: LyShark 内核开发系列教程
/
/
https:
/
/
www.cnblogs.com
/
LyShark
/
articles
/
16784393.html
#include <ntddk.h>
#include <ntstrsafe.h>
typedef struct _IO_TIMER
{
INT16
Type
;
INT16 TimerFlag;
LONG32 Unknown;
LIST_ENTRY TimerList;
PVOID TimerRoutine;
PVOID Context;
PVOID DeviceObject;
}IO_TIMER,
*
PIO_TIMER;
/
/
得到IoInitializeTimer基址
PVOID GetIoInitializeTimerAddress()
{
PVOID VariableAddress
=
0
;
UNICODE_STRING uioiTime
=
{
0
};
RtlInitUnicodeString(&uioiTime, L
"IoInitializeTimer"
);
VariableAddress
=
(PVOID)MmGetSystemRoutineAddress(&uioiTime);
if
(VariableAddress !
=
0
)
{
return
VariableAddress;
}
return
0
;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(
"卸载完成... \n"
);
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint((
"hello lyshark.com \n"
));
/
/
得到基址
PUCHAR IoInitializeTimer
=
GetIoInitializeTimerAddress();
DbgPrint(
"IoInitializeTimer Address = %p \n"
, IoInitializeTimer);
/
/
搜索IoTimerQueueHead地址
/
*
nt!IoInitializeTimer
+
0x5d
:
fffff806`
349963cd
488d5008
lea rdx,[rax
+
8
]
fffff806`
349963d1
48897018
mov qword ptr [rax
+
18h
],rsi
fffff806`
349963d5
4c8d05648de0ff
lea r8,[nt!IopTimerLock (fffff806`
3479f140
)]
fffff806`
349963dc
48897820
mov qword ptr [rax
+
20h
],rdi
fffff806`
349963e0
488d0d99f6cdff
lea rcx,[nt!IopTimerQueueHead (fffff806`
34675a80
)]
fffff806`
349963e7
e8c43598ff call nt!ExInterlockedInsertTailList (fffff806`
343199b0
)
fffff806`
349963ec
33c0
xor eax,eax
*
/
INT32 iOffset
=
0
;
PLIST_ENTRY IoTimerQueueHead
=
NULL;
PUCHAR StartSearchAddress
=
IoInitializeTimer;
PUCHAR EndSearchAddress
=
IoInitializeTimer
+
0xFF
;
UCHAR v1
=
0
, v2
=
0
, v3
=
0
;
for
(PUCHAR i
=
StartSearchAddress; i < EndSearchAddress; i
+
+
)
{
if
(MmIsAddressValid(i) && MmIsAddressValid(i
+
1
) && MmIsAddressValid(i
+
2
))
{
v1
=
*
i;
v2
=
*
(i
+
1
);
v3
=
*
(i
+
2
);
/
/
fffff806`
349963e0
48
8d
0d
99
f6 cd ff lea rcx,[nt!IopTimerQueueHead (fffff806`
34675a80
)]
if
(v1
=
=
0x48
&& v2
=
=
0x8d
&& v3
=
=
0x0d
)
{
memcpy(&iOffset, i
+
3
,
4
);
IoTimerQueueHead
=
(PLIST_ENTRY)(iOffset
+
(ULONG64)i
+
7
);
DbgPrint(
"IoTimerQueueHead = %p \n"
, IoTimerQueueHead);
break
;
}
}
}
/
/
枚举列表
KIRQL OldIrql;
/
/
获得特权级
OldIrql
=
KeRaiseIrqlToDpcLevel();
if
(IoTimerQueueHead && MmIsAddressValid((PVOID)IoTimerQueueHead))
{
PLIST_ENTRY NextEntry
=
IoTimerQueueHead
-
>Flink;
while
(MmIsAddressValid(NextEntry) && NextEntry !
=
(PLIST_ENTRY)IoTimerQueueHead)
{
PIO_TIMER Timer
=
CONTAINING_RECORD(NextEntry, IO_TIMER, TimerList);
if
(Timer && MmIsAddressValid(Timer))
{
DbgPrint(
"IO对象地址: %p \n"
, Timer);
}
NextEntry
=
NextEntry
-
>Flink;
}
}
/
/
恢复特权级
KeLowerIrql(OldIrql);
Driver
-
>DriverUnload
=
UnDriver;
return
STATUS_SUCCESS;
}
|
运行这段源代码,并可得到以下输出,由于没有IO定时器所以输出结果是空的:
至此IO定时器的枚举就介绍完了,在教程中你已经学会了使用特征码定位这门技术,相信你完全可以输出内核中想要得到的任何结构体。
更多【 驱动开发:内核枚举IoTimer定时器】相关视频教程:www.yxfzedu.com