Исследование функции SetTimer

Дата публикации 12 авг 2009

Исследование функции SetTimer — Архив WASM.RU



На днях, во время реверса одной программы, задалась вопросом, как вывести список таймеров, установленных win32 - функцией SetTimer (а если быть конкретнее, то мне нужны были адреса процедур - lpTimerFunc). Конечно, с практической точки зрения это малополезно, а с теоретической - очень даже. Что потребуется для успешного восприятия нижеизложенного? Из инструментов:

  • IDA
  • Наборы системных файлов для разных билдов Windows
  • Отладочные символы
  • msdn

Приступим непосредственно к исследованию. Сначала приведу прототип функции SetTimer (в процессе изложения удобно иметь его перед глазами)

Код (Text):
  1.  
  2. UINT_PTR SetTimer(      
  3.     HWND hWnd,
  4.     UINT_PTR nIDEvent,
  5.     UINT uElapse,
  6.     TIMERPROC lpTimerFunc
  7. );

Путешествия в недры дизассемблированного кода начнем с юзермода. Когда мы вызываем функцию SetTimer из user32.dll вызывается сервис win32k.sys (это видно по номеру сервиса, который >1000h) NtUserSetTimer

Код (Text):
  1.  
  2. ; UINT __stdcall NtUserSetTimer(HWND hWnd, UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc)
  3. public _NtUserSetTimer@16
  4. _NtUserSetTimer@16 proc near
  5.  
  6. hWnd= dword ptr  4
  7. nIDEvent= dword ptr  8
  8. uElapse= dword ptr  0Ch
  9. lpTimerFunc= dword ptr  10h
  10.  
  11. mov     eax, 121Eh
  12. mov     edx, 7FFE0300h
  13. call    dword ptr [edx]
  14. retn    10h
  15. _NtUserSetTimer@16 endp

Далее управление передается в ядро. Что ж, грузим в IDA win32k.sys

Обратимся к листингу NtUserSetTimer:

Код (Text):
  1.  
  2. ...
  3. .text:BF803A10                 mov     ecx, [ebp+hwnd]
  4. .text:BF803A13                 test    ecx, ecx
  5. .text:BF803A15                 jz      short loc_BF803A4C
  6. .text:BF803A17                 call    @ValidateHwnd@4 ; ValidateHwnd(x)
  7. .text:BF803A1C                 test    eax, eax
  8. .text:BF803A1E                 jz      short loc_BF8039F3
  9. .text:BF803A20
  10. .text:BF803A20 loc_BF803A20:                           ; CODE XREF: NtUserSetTimer(x,x,x,x)+49j
  11. .text:BF803A20                 mov     edx, [ebp+uElapse]
  12. .text:BF803A23                 cmp     edx, USER_TIMER_MINIMUM
  13. .text:BF803A26                 jb      short set_minimum
  14. .text:BF803A28
  15. .text:BF803A28 loc_BF803A28:                           ; CODE XREF: NtUserSetTimer(x,x,x,x)-7j
  16. .text:BF803A28                 mov     ecx, USER_TIMER_MAXIMUM
  17. .text:BF803A2D                 cmp     edx, ecx
  18. .text:BF803A2F                 ja      short set_maximum
  19. .text:BF803A31
  20. .text:BF803A31 loc_BF803A31:                           ; CODE XREF: NtUserSetTimer(x,x,x,x)-Cj
  21. .text:BF803A31                 push    [ebp+lpTimerFunc]
  22. .text:BF803A34                 push    edx
  23. .text:BF803A35                 push    [ebp+nIDEvent]
  24. .text:BF803A38                 push    eax
  25. .text:BF803A39                 call    __SetTimer@16   ; _SetTimer(x,x,x,x)
  26.  
  27. ...

Первым делом вызывается функция ValidateHwnd (подробнее о ней можно почитать в статье Twister-а http://www.wasm.ru/article.php?article=window_inject). Функция возвращает указатель на структуру tagWND. Этот указатель затем передается во внутреннюю функцию __SetTimer. В NtUserSetTimer проверяется параметр uElapse (об этом написано в msdn). Если быть точнее, то он должен быть USER_TIMER_MINIMUM<=uElapse<=USER_TIMER_MAXIMUM. Константы USER_TIMER_MINIMUM и USER_TIMER_MAXIMUM равны соответственно 0xA и 0x7FFFFFFF. Идем дальше... Заглянем в функцию __SetTimer, которая сводится к вызову InternalSetTimer.

Код (Text):
  1.  
  2. ...
  3. .text:BF803A5B                 mov     esi, [ebp+ptagWND]
  4. ...
  5. .text:BF803A77 loc_BF803A77:                           ; CODE XREF: _SetTimer(x,x,x,x)+Bj
  6. .text:BF803A77                 push    0
  7. .text:BF803A79                 push    [ebp+lpTimerFunc]
  8. .text:BF803A7C                 push    [ebp+uElapse]
  9. .text:BF803A7F                 push    [ebp+nIDEvent]
  10. .text:BF803A82                 push    esi
  11. .text:BF803A83                 call    _InternalSetTimer@20 ; InternalSetTimer(x,x,x,x,x)
...

Далее смотрим _InternalSetTimer. Здесь-то и начинается самое интересное:

Код (Text):
  1.  
  2. .text:BF803894 ; __stdcall InternalSetTimer(x, x, x, x, x)
  3. .text:BF803894 _InternalSetTimer@20 proc near          ; CODE XREF: zzzUpdateCursorImage()-14p
  4. .text:BF803894                                         ; _SetTimer(x,x,x,x)+2Ep ...
  5. .text:BF803894
  6. .text:BF803894 hwnd            = dword ptr  8
  7. .text:BF803894 IDEvent         = dword ptr  0Ch
  8. .text:BF803894 uElapse         = dword ptr  10h
  9. .text:BF803894 TimerFunction   = dword ptr  14h
  10. .text:BF803894 unknown         = dword ptr  18h
  11. .text:BF803894
  12. .text:BF803894 ; FUNCTION CHUNK AT .text:BF80384C SIZE 00000043 BYTES
  13. .text:BF803894
  14. .text:BF803894                 mov     edi, edi
  15. .text:BF803896                 push    ebp
  16. .text:BF803897                 mov     ebp, esp
  17. .text:BF803899                 push    ebx
  18. .text:BF80389A                 push    esi
  19. .text:BF80389B                 push    edi
  20. .text:BF80389C                 push    USER_TIMER_MINIMUM
  21. .text:BF80389E                 pop     eax
  22. .text:BF80389F                 xor     edi, edi
  23. .text:BF8038A1                 cmp     [ebp+uElapse], eax
  24. .text:BF8038A4                 jb      short set_el_minimum
  25. .text:BF8038A6
  26. .text:BF8038A6 loc_BF8038A6:                           ; CODE XREF: InternalSetTimer(x,x,x,x,x)-45j
  27. .text:BF8038A6                 mov     eax, USER_TIMER_MAXIMUM
  28. .text:BF8038AB                 cmp     [ebp+uElapse], eax
  29. .text:BF8038AE                 ja      short set_el_maximum

Первым делом, как видно, проводятся уже знакомые проверки параметра uElapse. Затем вызывается внутренняя функция _FindTimer. Затем выделяется память под структуру, содержащую информацию о таймере

Код (Text):
  1.  
  2. .text:BF8038C6                 push    30h             ; NumberOfBytes
  3. .text:BF8038C8                 push    10h             ; char
  4. .text:BF8038CA                 push    edi             ; int
  5. .text:BF8038CB                 push    edi             ; int
  6. .text:BF8038CC                 call    _HMAllocObject@16 ; HMAllocObject(x,x,x,x)
  7. .text:BF8038D1                 mov     esi, eax
  8. .text:BF8038D3                 cmp     esi, edi
  9. .text:BF8038D5                 jz      error_allocate

Заполнение структуры (указываю только на интересные поля) - это процедура таймера, ид таймера и следующий таймер в списке

Код (Text):
  1.  
  2. .text:BF8038E7                 mov     eax, [ebp+nIDEvent]
  3. .text:BF8038EA
  4. .text:BF8038EA loc_BF8038EA:                           ; CODE XREF: InternalSetTimer(x,x,x,x,x)+150j
  5. ; записываем в структуру по смещению 18h ид таймера
  6. .text:BF8038EA                 mov     [esi+18h], eax
  7. .text:BF8038ED                 mov     eax, _gptmrFirst
  8. ; записываем адрес следующей за нами структуры таймера
  9. .text:BF8038F2                 mov     [esi+8], eax
  10. .text:BF8038F5                 mov     [esi+0Ch], edi
  11. .text:BF8038F8                 mov     eax, _gptmrFirst
  12. .text:BF8038FD                 cmp     eax, edi
  13. .text:BF8038FF                 jz      short ifFirst
  14. .text:BF803901                 mov     [eax+0Ch], esi
  15. .text:BF803904
  16. .text:BF803904 ifFirst:                                ; CODE XREF: InternalSetTimer(x,x,x,x,x)+6Bj
  17. .text:BF803904                 mov     _gptmrFirst, esi
  18. .text:BF80390A

Тут появляется весьма интересная переменная _gptmrFirst - которая представляет собой указатель на односвязный список таймеров, зарегистрированных в системе. Последний зарегистрированный таймер помещается в начало списка.

По смещению 0x28 в нашей структуре содержится процедура таймера (ради которой и пришлось расковырять эту функцию):

Код (Text):
  1.  
  2. .text:BF80394E                 mov     eax, [ebp+TimerFunction]
  3. .text:BF803951                 mov     [esi+28h], eax

Приведенной выше информации достаточно для вывода информации о таймерах в winxp. Объявим такую структуру

Код (Text):
  1.  
  2. typedef struct _timer_struct_xp
  3. {
  4.  DWORD unkn01[2];
  5.  _timer_struct_xp * nextTimer;
  6.  DWORD unkn02[2];
  7.  PULONG ptagWND;
  8.  DWORD nIDEvent;
  9.  DWORD unkn03[3];
  10.  PVOID TimerFunc;  
  11.  DWORD unkn04;
  12. }timer_struct_xp,*ptimer_struct_xp;

Обратите внимание на поле ptagWND – указатель на структуру tagWND для окна, хендл которого передается как первый аргумент SetTimer. Нужно оно для идентификации процесса, связанного с таймером. Дело в том, что в структуре tagWND по смещению 8 лежит указатель на структуру WIN32THREAD, первое поле которой - указатель ETHREAD. А имея в наличии ETHREAD можно определить процесс, которому принадлежит нить.

Все бы ничего, но сначала необходимо найти переменную _gptmrFirst. Из вышеизложенного видно, что дизассемблировать процедуру за процедурой будет не очень рационально, да и ненадежно. Более красивого способа поиска этой переменной я не нашла, поэтому я определила только адреса в разных версиях Windows.

На этом можно было бы поставить точку, но тогда статья была бы неполной. Стоит сказать несколько слов о том, как обстоят дела в Windows 7 (бета и RC). Какие же изменения произошли в этой версии Винды? Во-первых структура под таймер теперь выделяется размером не 30h, а 44h

Код (Text):
  1.  
  2. .text:BF8D73C2                 push    44h             ; NumberOfBytes
  3. .text:BF8D73C4                 push    10h             ; char
  4. .text:BF8D73C6                 push    esi             ; int
  5. .text:BF8D73C7                 push    [ebp+var_4]     ; int
  6. .text:BF8D73CA                 call    _HMAllocObject@16 ; HMAllocObject(x,x,x,x)
  7. .text:BF8D73CF                 mov     esi, eax

Во-вторых переменная, хранящая указатель на список таймеров (который, в-третьих, из односвязного трансформировался в двусвязный) теперь именуется _gtmrListHead.

Код (Text):
  1.  
  2. .text:BF8D741E                 mov     [esi+1Ch], eax
  3. .text:BF8D7421                 mov     ecx, _gtmrListHead
  4. .text:BF8D7427                 lea     eax, [esi+0Ch]
  5. .text:BF8D742A                 mov     [eax], ecx
  6. .text:BF8D742C                 mov     dword ptr [eax+4], offset _gtmrListHead
  7. .text:BF8D7433                 mov     [ecx+4], eax
  8. .text:BF8D7436                 mov     _gtmrListHead, eax

Ну и, в-четвертых, смещение нужного нам поля с адресом процедуры таймера тоже поменялось

Код (Text):
  1.  
  2. .text:BF8D7483                 mov     eax, [ebp+lpTimerFunc]
  3. .text:BF8D7486                 mov     [esi+2Ch], eax

Кстати говоря, в Висте дела обстоят аналогичным образом. Все поля структуры реверсить незачем (может как-нибудь в другой раз), следующего объявления будет вполне достаточно

Код (Text):
  1.  
  2. typedef struct _timer_struct_vistawin7
  3. {
  4.  DWORD unkn01[3];
  5.  LIST_ENTRY leTimerList;
  6.  PULONG ptagWND;
  7.  DWORD unkn03[5];  
  8.  PVOID TimerFunc;
  9. /*---*/
  10. }timer_struct_vistawin7,*ptimer_struct_vistawin7;

Теперь, когда известно где брать нужную информацию, в коде драйвера, выводящего DbgPrint – ом поля структуры таймера, пропишем в коде RVA переменных (_gptmrFirst и _gtmrListHead) для разных билдов

Код (Text):
  1.  
  2. switch(*NtBuildNumber)
  3.     {
  4.     case 7100:
  5.         // Win7 RC
  6.         uRVA = 0x21D970;
  7.         break;
  8.     case 6001:
  9.         // Vista
  10.         uRVA = 0x1E2110;
  11.         break;
  12.     case 7000:
  13.         // Beta Win7
  14.         uRVA = 0x2189F0;
  15.         break;
  16.  
  17.     case 2600:
  18.         // SP2
  19.         uRVA = 0x1A8914;
  20.         break;
  21.  
  22.     default:
  23.         break;
  24.  
  25.     }

К сожалению, у меня не было в наличии большого числа различных win32k.sys, но, как мне кажется и этого достаточно. Я не люблю прописывать смещения, но в данном случае это оправдано. Кто хочет, может встроить любой понравившийся дизасм длин и отдельно написать код поиска для XP и Vista. Перед этим желающим дизассемблировать надо будет найти ShadowSSDT и определить номер сервиса NtUserSetTimer.

Так же необходимо определить базу win32k.sys. Привожу две процедуры вывода

Код (Text):
  1.  
  2. VOID KePrintTimersWin7Vista(DWORD dwBase,DWORD dwRVA)
  3. {
  4.     PLIST_ENTRY pleTimerHead;
  5.  
  6.     ptimer_struct_vistawin7 ptsCurrent;
  7.  
  8.     DWORD pWND;
  9.  
  10.     ULONG ptCurThread,pProcess;
  11.  
  12.     if(!dwBase) return;
  13.  
  14.     // RVA переменной + база win32k.sys
  15.     pleTimerHead = (PLIST_ENTRY)(dwRVA+dwBase);
  16.    
  17.     ptsCurrent = CONTAINING_RECORD(pleTimerHead->Flink,
  18.                 timer_struct_vistawin7,
  19.                 leTimerList);
  20.  
  21.     DWORD dwCount = 0;
  22.  
  23.     do
  24.     {
  25.    
  26.         if(MmIsAddressValid((PVOID)ptsCurrent))
  27.      
  28.         {
  29.  
  30.             DbgPrint("KePrintTimers: Current timer %X; Next timer %X; Timer func %X\n",
  31.                 ptsCurrent,
  32.                 ptsCurrent->leTimerList.Flink,
  33.                 ptsCurrent->TimerFunc
  34.                 );
  35.  
  36.             pWND = (DWORD)ptsCurrent->ptagWND;
  37.  
  38.             // определение по указателю на tagWND имени процесса
  39.             if(pWND)
  40.             {
  41.  
  42.                 DbgPrint(" Timer window handle %X\n",
  43.                     *(PULONG)(pWND));
  44.                 // по смещению 8 в структуре tagWND лежит указатель на Win32Thread
  45.                 // в этой структуре первый дворд - указатель на ETHREAD
  46.                 ptCurThread = *(*(PULONG *)(pWND+8));
  47.  
  48.                 if(ptCurThread)
  49.                 {
  50.                   DbgPrint(" Timer ETHREAD %X\n",ptCurThread);
  51.  
  52.                   // смещение KPROCESS в ETHREAD
  53.                   if(*NtBuildNumber<7100)
  54.                   {
  55.                    
  56.                       pProcess = *(PULONG)(ptCurThread+0x144);
  57.                  
  58.                   }
  59.                   else
  60.                   {
  61.                  
  62.                       pProcess = *(PULONG)(ptCurThread+0x150);
  63.  
  64.                   }
  65.                   DbgPrint(" EPROCESS =%X\n",pProcess);
  66.  
  67.                   if(pProcess)
  68.                   {
  69.                       DbgPrint(" Process name = %s\n",
  70.                           PsGetProcessImageFileName((PEPROCESS)pProcess));
  71.                   }
  72.  
  73.                 }
  74.             }
  75.  
  76.  
  77.             ptsCurrent = CONTAINING_RECORD(ptsCurrent->leTimerList.Flink,
  78.                 timer_struct_vistawin7,
  79.                 leTimerList);
  80.  
  81.             dwCount ++;
  82.    
  83.     }
  84.     else
  85.     {
  86.             DbgPrint("KePrintTimers: Invalid address\n");
  87.  
  88.             break;
  89.     }
  90.  
  91.     }while(ptsCurrent->leTimerList.Flink!=pleTimerHead);
  92.  
  93.     DbgPrint("KePrintTimers: Timers count =%d\n",dwCount);
  94.  
  95.  
  96. }

Как вы могли заметить смещение PKPROCESS в ETHREAD поменялось в Windows 7 RC, поэтому введена дополнительная проверка.

Код (Text):
  1.  
  2. VOID KePrintTimersXP(DWORD dwBase,DWORD dwRVA)
  3. {
  4.     ptimer_struct_xp ptsInstalledTimers;
  5.  
  6.     if(!dwBase) return;
  7.  
  8.     ptsInstalledTimers = *(ptimer_struct_xp *)(dwRVA + dwBase);
  9.  
  10.     DWORD dwCount = 0;
  11.  
  12.     DWORD pWND;
  13.  
  14.     PETHREAD ptCurThread;
  15.  
  16.  
  17.     while(ptsInstalledTimers)
  18.     {
  19.    
  20.         if(MmIsAddressValid((PVOID)ptsInstalledTimers))
  21.      
  22.         {
  23.             DbgPrint("KePrintTimers: Address valid\n");
  24.  
  25.             DbgPrint("KePrintTimers: Current timer %X; Next timer %X; Timer func %X; nIDEvent %X\n",
  26.                 ptsInstalledTimers,
  27.                 ptsInstalledTimers->nextTimer,
  28.                 ptsInstalledTimers->TimerFunc,
  29.                 ptsInstalledTimers->nIDEvent);
  30.  
  31.             pWND = (DWORD)ptsInstalledTimers->ptagWND;
  32.  
  33.             // определение по указателю на tagWND имени процесса
  34.             if(pWND)
  35.             {
  36.  
  37.                 DbgPrint(" Timer window handle %X\n",
  38.                     *(PULONG)(pWND));
  39.                 // по смещению 8 в структуре tagWND лежит указатель на Win32Thread
  40.                 // в этой структуре первый дворд - указатель на ETHREAD
  41.                 ptCurThread = *(PETHREAD *)(*(PULONG)(pWND+8));
  42.  
  43.                 if(ptCurThread)
  44.                 {
  45.                   DbgPrint(" Timer ETHREAD %X\n",ptCurThread);
  46.  
  47.                   DbgPrint(" EPROCESS =%X\n",ptCurThread->ThreadsProcess);
  48.  
  49.                   if(ptCurThread->ThreadsProcess)
  50.                   {
  51.                       DbgPrint(" Process name = %s\n",
  52.                           PsGetProcessImageFileName(ptCurThread->ThreadsProcess));
  53.                   }
  54.  
  55.                 }
  56.             }
  57.             ptsInstalledTimers = ptsInstalledTimers->nextTimer;
  58.  
  59.             dwCount ++;
  60.    
  61.     }
  62.     else
  63.     {
  64.             DbgPrint("KePrintTimers: Invalid address\n");
  65.  
  66.             break;
  67.     }
  68.  
  69. }
  70.  
  71.     DbgPrint("KePrintTimers: Timers count =%d\n",dwCount);
  72.  
  73.    
  74. }

Как видите все предельно просто.

Как проверить, что все правильно? Инсталлировать таймер в юзермоде и посмотреть будет ли он присутствовать в выводе.

nIDEvent – то, что вернула SetTimer.

На этом статью можно завершить, надеюсь, кому-нибудь было интересно. © lhc645


0 2.319
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532