这个是整个进程模块最耗时的时间,因为涉及到逆向的过程;
这里的定时器是Windows进程所有的定时器,并不是DPC的定时器;
逆向过程是首先找到user32.dll的SetTimer
这个函数;
1
2
3
4
|
UINT_PTR __stdcall SetTimer(HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc)
{
return
NtUserSetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc,
0
);
}
|
可以看出,他调用了win32u.dll
的导出函数NtUserSetTimer
,注意,这个系统版本是19044,在低版本win10甚至更低版本,是没有win32u.dll
的,是直接通过user32.dll
syscall进入内核;
这样,直接去win32kfull.sys
查看这个内核函数,IDA F5
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
|
UINT_PTR __fastcall NtUserSetTimer(HWND hWnd, UINT_PTR
id
, unsigned
int
elapse, UINT_PTR proc, unsigned
int
unk_flags)
{
UINT_PTR timerId;
/
/
rbx
__int64 v10;
/
/
rax
__int64 CurrentThreadWin32Thread;
/
/
rax
UINT64 window_instance;
/
/
rbp
unsigned
int
_elapse;
/
/
edi
unsigned
int
_unk_flags;
/
/
esi
__int64 CurrentProcessWin32Process;
/
/
rax
__int64 CurrentProcessWin32Process_1;
/
/
r8
__int64 v17;
/
/
rax
__int64 errcode;
/
/
rcx
EnterCrit(
0i64
,
0i64
);
timerId
=
0i64
;
if
( !
*
(_QWORD
*
)(SGDGetUserSessionState()
+
8
)
|| (v10
=
SGDGetUserSessionState(), !ExIsResourceAcquiredSharedLite(
*
(PERESOURCE
*
)(v10
+
8
))) )
{
if
( (gdwExtraInstrumentations &
1
) !
=
0
)
KeBugCheckEx(
0x164u
,
0x2Aui64
,
0i64
,
0i64
,
0i64
);
DbgkWerCaptureLiveKernelDump(aNtuser,
400i64
,
42i64
,
0i64
,
0i64
,
0i64
,
0i64
,
0i64
,
0
);
}
CurrentThreadWin32Thread
=
PsGetCurrentThreadWin32Thread();
+
+
*
(_DWORD
*
)(CurrentThreadWin32Thread
+
48
);
if
( !hWnd )
{
window_instance
=
0i64
;
hwnd_valid:
_elapse
=
10
;
if
( elapse >
=
10
)
/
/
如果间隔小于
10ms
,那就赋值
10
,因为时钟中断
_elapse
=
elapse;
_unk_flags
=
unk_flags;
if
( _elapse >
0x7FFFFFFF
)
_elapse
=
0x7FFFFFFF
;
if
( unk_flags
=
=
0x7FFFFFF5
)
/
/
正常调用是
0
{
_unk_flags
=
0x7FFFFFFF
-
_elapse;
}
else
if
( unk_flags !
=
-
1
&& (_elapse
+
unk_flags < _elapse || _elapse
+
unk_flags >
0x7FFFFFFF
) )
{
errcode
=
87i64
;
goto error;
}
if
( !window_instance )
goto driectly_set;
/
/
hwnd是一 直接设置
CurrentProcessWin32Process
=
PsGetCurrentProcessWin32Process(
0x7FFFFFFFi64
);
CurrentProcessWin32Process_1
=
CurrentProcessWin32Process;
if
( CurrentProcessWin32Process )
CurrentProcessWin32Process_1
=
-
(__int64)(
*
(_QWORD
*
)CurrentProcessWin32Process !
=
0i64
) & CurrentProcessWin32Process;
if
( CurrentProcessWin32Process_1
=
=
*
(_QWORD
*
)(
*
(_QWORD
*
)(window_instance
+
0x10
)
+
0x1A0i64
) )
/
/
不能跨进程设置 tagWND
*
spwndParent;
{
driectly_set:
timerId
=
InternalSetTimer((void
*
)window_instance,
id
, _elapse, (void
*
)proc, _unk_flags,
0
);
goto LABEL_18;
}
errcode
=
5i64
;
error:
UserSetLastError(errcode);
goto LABEL_18;
}
window_instance
=
ValidateHwnd(hWnd);
/
/
把hwnd转换成指针 tagWND
if
( window_instance )
goto hwnd_valid;
LABEL_18:
v17
=
PsGetCurrentThreadWin32Thread();
-
-
*
(_DWORD
*
)(v17
+
48
);
UserSessionSwitchLeaveCrit();
return
timerId;
}
|
可以看到,如果HWND有效,最终会转换成PWND
这个结构,而如果HWND是NULL,那么则是插入全局的Timer,不针对窗口,那么接下来看InternalSetTimer
函数
事实上,这个函数中出现了两个链表,之前猜测定时器可能放置在链表中;
其中一个链表是timer = (UINT64 *)((char *)&gTimerHashTable + 0x10 * ((BYTE1(pwnd) + (unsigned __int8)nIDEvent) & 0x3F));// Timer基于窗口?
也就是gTimerHashTable
,这个链表如其名字,实际上他就是一个哈希表,哈希函数是
hash(x,y)=0x10*( ()(Byte)(pwnd>>8)+(Byte)nIdEvent)) & 0x3F)
,不难看出,&0x3F 说明了这个哈希表大小是64个链表,有点像哈希桶的感觉,而0x10就是LIST_ENTRY的大小,
事实上,Windows就算通过这个根据PWND+ID(时钟的)+Hash函数快速定位到对应的链表上,FindTimer
这个函数就可以看到上述代码;
上述的hash表挂在PTIMER+0x70
的位置
1
2
3
4
|
v30
=
(char
*
)(newAllocTimer
+
0x70
),
v31
=
(char
*
)&gTimerHashTable
+
16
*
(((unsigned __int8)v14
+
(unsigned __int8)
*
(_DWORD
*
)(newAllocTimer
+
96
)) &
0x3F
),
v32
=
(char
*
*
)
*
((_QWORD
*
)v31
+
1
),
|
而另一个表叫做gtmrListHead
则较为暴力,就是一个双向链表,连着所有PTIMER结构,挂在0x48
的位置
1
2
3
4
5
6
|
*
(_QWORD
*
)(newAllocTimer
+
96
)
=
nIDEvent;
if
(
*
(_QWORD
*
)(gtmrListHead
+
8i64
) !
=
gtmrListHead
|| (
*
(_QWORD
*
)(newAllocTimer
+
0x50
)
=
gtmrListHead,
*
v29
=
gtmrListHead,
*
(_QWORD
*
)(gtmrListHead
+
8i64
)
=
v29,
gtmrListHead
=
newAllocTimer
+
0x48
,
|
接着看下面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
HMAssignmentLock(v39,
0i64
);
*
(_DWORD
*
)(header
+
0x28
)
=
dwElapse_1_1;
*
(_DWORD
*
)(header
+
0x34
)
=
dwElapse_1_1;
*
(_QWORD
*
)(header
+
0x20
)
=
pTimerFunc;
*
(_QWORD
*
)(header
+
0x68
)
=
0i64
;
if
( (v13 &
0x200
) !
=
0
)
*
(_DWORD
*
)(header
+
44
)
=
flags;
*
(_DWORD
*
)(header
+
0x80
)
=
(MEMORY[
0xFFFFF78000000320
]
*
(unsigned __int64)MEMORY[
0xFFFFF78000000004
]) >>
24
;
if
( (v13 &
0x80u
) !
=
0
)
{
v13 &
=
~
0x80u
;
}
else
if
( (v13 &
0x100
) !
=
0
)
{
*
(_QWORD
*
)(header
+
0x68
)
=
thread;
}
*
(_DWORD
*
)(header
+
48
)
=
v13 |
8
;
*
(_QWORD
*
)(header
+
24
)
=
threadInfo;
|
由此可以推断出,PTIMER的结构应该是下面的结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
typedef struct timer_t{
HEAD head;
/
/
0
void
*
pfn;
/
/
20
DWORD32 elapse;
/
/
28
DWORD32 flags;
/
/
2c
DWORD32 unkFlags;
/
/
30
DWORD32 elapse1;
/
/
34
char padding[
0x10
];
/
/
38
LIST_ENTRY list1;
/
/
链接的是gtmrListHead
/
/
48
void
*
spwnd;
/
/
58
UINT64
id
;
/
/
60
void
*
threadObject;
/
/
68
LIST_ENTRY list2;
/
/
Hash
链接gTimerHashTable
}
*
ptimer_t;
|
其中HEAD是我参考xp源码得到的,结构如下
1
2
3
4
|
typedef struct _HEAD{
void
*
unk[
3
];
void
*
threadInfo;
}HEAD;
|
这个ThreadInfo结构是这样
1
2
3
4
|
typedef struct threadInfo{
WIN32THREAD w32thread;
UNK;
}
|
WIN32THREAD
的第一个成员就算改定时器所属的线程的EPROCESS
从HEAD
到threadInfo
均参考XP源码,经过验证发现没有问题;
而至于如何遍历进程的所有定时器呢?遍历hash链表实际上是画蛇添足了;因为根本不知道Timer的ID,所以0-63试还是很慢,不如直接遍历gtmrListHead
来;
接下来,就是获取gtmrListHead
,这里比较方便的是,无论是gtmrListHead
还是gTimerHashTable
都是win32kfull.sys
从win32kbase.sys
导出的,这个就比较方便;直接写一个遍历内核模块导出表的进行判断即可;
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
|
auto find_module_export(void
*
base,const char
*
name)
-
> void
*
{
/
/
为了确保查得到,需要进行附加explorer.exe 才能有地址空间 比如win32kxx.sys
/
/
要求调用这个函数的人必须是GUI线程 否则无法查找
/
/
explorer不一定能获取到?
if
(!MmIsAddressValid(base) || name
=
=
nullptr)
return
nullptr;
__try {
if
(
*
((unsigned short
*
)base) !
=
0x5A4D
) {
return
0
;
/
/
不是有效的PE文件
}
auto dosHeaders
=
(PIMAGE_DOS_HEADER)base;
auto ntHeaders
=
(PIMAGE_NT_HEADERS)(dosHeaders
-
>e_lfanew
+
(UINT_PTR)base);
auto exportDirectory
=
(PIMAGE_EXPORT_DIRECTORY)((PUCHAR)base
+
ntHeaders
-
>OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
/
/
nameTable存的是函数名的RVA
auto nameTable
=
(PULONG)(exportDirectory
-
>AddressOfNames
+
(PUCHAR)base);
/
/
索引到funcTable索引转换需要这个
auto ordinalTable
=
(PSHORT)(exportDirectory
-
>AddressOfNameOrdinals
+
(PUCHAR)base);
auto funcTable
=
(PULONG)(exportDirectory
-
>AddressOfFunctions
+
(PUCHAR)base);
for
(auto i
=
0ul
; i < exportDirectory
-
>NumberOfNames; i
+
+
) {
if
(strcmp((char
*
)base
+
nameTable[i], name)
=
=
0
) {
/
/
find
auto index
=
ordinalTable[i];
auto ret
=
(UINT_PTR)base
+
funcTable[index];
return
(void
*
)ret;
}
}
return
0
;
}
__except (
1
) {
return
nullptr;
}
}
|
所以通过遍历进程的所有线程来得到PEPROCESS,再通过threadInfo
来判断是不是该进程所属的定时器,如果是,那么就添加;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/
/
给入一个链表头 插入这里面 插入改进程的所有定时器
auto query_process_timer(__inout pfind_list_t head,HANDLE pid)
-
> void {
/
/
先询问进程的所有线程
auto tidArry
=
kprocess::query_threads_tid(pid);
if
(tidArry
=
=
nullptr)
return
;
for
(
int
i
=
0
;tidArry[i];i
+
+
) {
query_timer_count((PLIST_ENTRY)head, tidArry[i]);
}
ExFreePool(tidArry);
return
;
}
|
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
|
auto query_timer_count(PLIST_ENTRY head,HANDLE tid)
-
> unsigned
int
{
unsigned
int
count
=
0
;
PETHREAD thread{
0
};
auto status
=
PsLookupThreadByThreadId(tid, &thread);
if
(!NT_SUCCESS(status)) {
return
0
;
}
ObDereferenceObject(thread);
auto volatile gtmrListHead
=
(PLIST_ENTRY)
_Utils::find_module_export(_Utils::find_module_base(
"win32kbase.sys"
),
"gtmrListHead"
);
if
(gtmrListHead
=
=
nullptr)
return
0
;
/
/
这个不是
hash
链表
for
(auto entry
=
gtmrListHead
-
>Flink;
entry !
=
gtmrListHead; entry
=
entry
-
>Flink) {
auto item
=
CONTAINING_RECORD(entry, timer_t, list1);
/
/
这个地方疑似不能解引用 有时候PageFault
/
/
注意 这里的定时器有可能属于hwnd
=
=
0
的 因此最好判断threadInfo
if
((
*
(PETHREAD
*
)(item
-
>head.threadInfo))
=
=
thread
) {
if
(find(head, item)) {
continue
;
}
else
{
auto _item
=
(pfind_list_t)ExAllocatePoolWithTag(PagedPool, sizeof find_list_t,
'list'
);
if
(item
=
=
nullptr) {
ONLY_DEBUG_BREAK;
}
_item
-
>timer
=
item;
InsertHeadList(head, (PLIST_ENTRY)(_item));
count
+
+
;
}
}
}
return
count;
}
|
(*(PETHREAD*)(item->head.threadInfo)
来判断线程,如此遍历;即可遍历到进程的所有定时器;
最后效果
更多【win11遍历进程定时器方法逆向】相关视频教程:www.yxfzedu.com