首页
社区
课程
招聘
[原创]win11遍历进程定时器方法逆向
发表于: 2023-5-14 11:02 8640

[原创]win11遍历进程定时器方法逆向

2023-5-14 11:02
8640

枚举定时器

这个是整个进程模块最耗时的时间,因为涉及到逆向的过程;

 

这里的定时器是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.dllsyscall进入内核;

 

这样,直接去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

 

HEADthreadInfo均参考XP源码,经过验证发现没有问题;

 

而至于如何遍历进程的所有定时器呢?遍历hash链表实际上是画蛇添足了;因为根本不知道Timer的ID,所以0-63试还是很慢,不如直接遍历gtmrListHead来;

 

接下来,就是获取gtmrListHead,这里比较方便的是,无论是gtmrListHead还是gTimerHashTable都是win32kfull.syswin32kbase.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)来判断线程,如此遍历;即可遍历到进程的所有定时器;
最后效果
图片描述


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 6
支持
分享
最新回复 (6)
雪    币: 1129
活跃值: (2731)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-5-15 01:21
0
雪    币: 2948
活跃值: (30846)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-5-15 09:21
1
雪    币: 844
活跃值: (9816)
能力值: ( LV13,RANK:385 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2023-5-15 10:22
0
雪    币: 3736
活跃值: (3867)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
感谢分享。
2023-8-5 11:12
0
雪    币: 1281
活跃值: (4535)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2024-2-15 23:30
0
雪    币: 1069
活跃值: (1010)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
很好
2024-2-19 10:15
0
游客
登录 | 注册 方可回帖
返回
//