29.12.2024
| Главная | Поиск | Регистрация | Профиль | Вход | Выход |
Категории
Разное [27]
Реклама
Реклама
Интересное
Главная » Статьи » Разное » Разное

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

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

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

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

UINT_PTR SetTimer( 
HWND hWnd,
UINT_PTR nIDEvent,
UINT uElapse,
TIMERPROC lpTimerFunc
);

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

; UINT __stdcall NtUserSetTimer(HWND hWnd, UINT nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc)
public _NtUserSetTimer@16
_NtUserSetTimer@16 proc near

hWnd= dword ptr 4
nIDEvent= dword ptr 8
uElapse= dword ptr 0Ch
lpTimerFunc= dword ptr 10h

mov eax, 121Eh
mov edx, 7FFE0300h
call dword ptr [edx]
retn 10h
_NtUserSetTimer@16 endp

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

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

... 
.text:BF803A10 mov ecx, [ebp+hwnd]
.text:BF803A13 test ecx, ecx
.text:BF803A15 jz short loc_BF803A4C
.text:BF803A17 call @ValidateHwnd@4 ; ValidateHwnd(x)
.text:BF803A1C test eax, eax
.text:BF803A1E jz short loc_BF8039F3
.text:BF803A20
.text:BF803A20 loc_BF803A20: ; CODE XREF: NtUserSetTimer(x,x,x,x)+49j
.text:BF803A20 mov edx, [ebp+uElapse]
.text:BF803A23 cmp edx, USER_TIMER_MINIMUM
.text:BF803A26 jb short set_minimum
.text:BF803A28
.text:BF803A28 loc_BF803A28: ; CODE XREF: NtUserSetTimer(x,x,x,x)-7j
.text:BF803A28 mov ecx, USER_TIMER_MAXIMUM
.text:BF803A2D cmp edx, ecx
.text:BF803A2F ja short set_maximum
.text:BF803A31
.text:BF803A31 loc_BF803A31: ; CODE XREF: NtUserSetTimer(x,x,x,x)-Cj
.text:BF803A31 push [ebp+lpTimerFunc]
.text:BF803A34 push edx
.text:BF803A35 push [ebp+nIDEvent]
.text:BF803A38 push eax
.text:BF803A39 call __SetTimer@16 ; _SetTimer(x,x,x,x)

...

Первым делом вызывается функция 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:BF803A5B mov esi, [ebp+ptagWND]
...
.text:BF803A77 loc_BF803A77: ; CODE XREF: _SetTimer(x,x,x,x)+Bj
.text:BF803A77 push 0
.text:BF803A79 push [ebp+lpTimerFunc]
.text:BF803A7C push [ebp+uElapse]
.text:BF803A7F push [ebp+nIDEvent]
.text:BF803A82 push esi
.text:BF803A83 call _InternalSetTimer@20 ; InternalSetTimer(x,x,x,x,x)
...

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

.text:BF803894 ; __stdcall InternalSetTimer(x, x, x, x, x)
.text:BF803894 _InternalSetTimer@20 proc near ; CODE XREF: zzzUpdateCursorImage()-14p
.text:BF803894 ; _SetTimer(x,x,x,x)+2Ep ...
.text:BF803894
.text:BF803894 hwnd = dword ptr 8
.text:BF803894 IDEvent = dword ptr 0Ch
.text:BF803894 uElapse = dword ptr 10h
.text:BF803894 TimerFunction = dword ptr 14h
.text:BF803894 unknown = dword ptr 18h
.text:BF803894
.text:BF803894 ; FUNCTION CHUNK AT .text:BF80384C SIZE 00000043 BYTES
.text:BF803894
.text:BF803894 mov edi, edi
.text:BF803896 push ebp
.text:BF803897 mov ebp, esp
.text:BF803899 push ebx
.text:BF80389A push esi
.text:BF80389B push edi
.text:BF80389C push USER_TIMER_MINIMUM
.text:BF80389E pop eax
.text:BF80389F xor edi, edi
.text:BF8038A1 cmp [ebp+uElapse], eax
.text:BF8038A4 jb short set_el_minimum
.text:BF8038A6
.text:BF8038A6 loc_BF8038A6: ; CODE XREF: InternalSetTimer(x,x,x,x,x)-45j
.text:BF8038A6 mov eax, USER_TIMER_MAXIMUM
.text:BF8038AB cmp [ebp+uElapse], eax
.text:BF8038AE ja short set_el_maximum

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

.text:BF8038C6 push 30h ; NumberOfBytes
.text:BF8038C8 push 10h ; char
.text:BF8038CA push edi ; int
.text:BF8038CB push edi ; int
.text:BF8038CC call _HMAllocObject@16 ; HMAllocObject(x,x,x,x)
.text:BF8038D1 mov esi, eax
.text:BF8038D3 cmp esi, edi
.text:BF8038D5 jz error_allocate

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

.text:BF8038E7 mov eax, [ebp+nIDEvent]
.text:BF8038EA
.text:BF8038EA loc_BF8038EA: ; CODE XREF: InternalSetTimer(x,x,x,x,x)+150j
; записываем в структуру по смещению 18h ид таймера
.text:BF8038EA mov [esi+18h], eax
.text:BF8038ED mov eax, _gptmrFirst
; записываем адрес следующей за нами структуры таймера
.text:BF8038F2 mov [esi+8], eax
.text:BF8038F5 mov [esi+0Ch], edi
.text:BF8038F8 mov eax, _gptmrFirst
.text:BF8038FD cmp eax, edi
.text:BF8038FF jz short ifFirst
.text:BF803901 mov [eax+0Ch], esi
.text:BF803904
.text:BF803904 ifFirst: ; CODE XREF: InternalSetTimer(x,x,x,x,x)+6Bj
.text:BF803904 mov _gptmrFirst, esi
.text:BF80390A

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

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

.text:BF80394E mov eax, [ebp+TimerFunction]
.text:BF803951 mov [esi+28h], eax

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

typedef struct _timer_struct_xp
{
DWORD unkn01[2];
_timer_struct_xp * nextTimer;
DWORD unkn02[2];
PULONG ptagWND;
DWORD nIDEvent;
DWORD unkn03[3];
PVOID TimerFunc;
DWORD unkn04;
}timer_struct_xp,*ptimer_struct_xp;

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

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

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

.text:BF8D73C2 push 44h ; NumberOfBytes
.text:BF8D73C4 push 10h ; char
.text:BF8D73C6 push esi ; int
.text:BF8D73C7 push [ebp+var_4] ; int
.text:BF8D73CA call _HMAllocObject@16 ; HMAllocObject(x,x,x,x)
.text:BF8D73CF mov esi, eax

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

.text:BF8D741E mov [esi+1Ch], eax
.text:BF8D7421 mov ecx, _gtmrListHead
.text:BF8D7427 lea eax, [esi+0Ch]
.text:BF8D742A mov [eax], ecx
.text:BF8D742C mov dword ptr [eax+4], offset _gtmrListHead
.text:BF8D7433 mov [ecx+4], eax
.text:BF8D7436 mov _gtmrListHead, eax

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

.text:BF8D7483 mov eax, [ebp+lpTimerFunc]
.text:BF8D7486 mov [esi+2Ch], eax

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

typedef struct _timer_struct_vistawin7
{
DWORD unkn01[3];
LIST_ENTRY leTimerList;
PULONG ptagWND;
DWORD unkn03[5];
PVOID TimerFunc;
/*---*/
}timer_struct_vistawin7,*ptimer_struct_vistawin7;

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

switch(*NtBuildNumber)
{
case 7100:
// Win7 RC
uRVA = 0x21D970;
break;
case 6001:
// Vista
uRVA = 0x1E2110;
break;
case 7000:
// Beta Win7
uRVA = 0x2189F0;
break;

case 2600:
// SP2
uRVA = 0x1A8914;
break;

default:
break;

}

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

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

VOID KePrintTimersWin7Vista(DWORD dwBase,DWORD dwRVA)
{
PLIST_ENTRY pleTimerHead;

ptimer_struct_vistawin7 ptsCurrent;

DWORD pWND;

ULONG ptCurThread,pProcess;

if(!dwBase) return;

// RVA переменной + база win32k.sys
pleTimerHead = (PLIST_ENTRY)(dwRVA+dwBase);

ptsCurrent = CONTAINING_RECORD(pleTimerHead->Flink,
timer_struct_vistawin7,
leTimerList);

DWORD dwCount = 0;

do
{

if(MmIsAddressValid((PVOID)ptsCurrent))

{

DbgPrint("KePrintTimers: Current timer %X; Next timer %X; Timer func %X\n",
ptsCurrent,
ptsCurrent->leTimerList.Flink,
ptsCurrent->TimerFunc
);

pWND = (DWORD)ptsCurrent->ptagWND;

// определение по указателю на tagWND имени процесса
if(pWND)
{

DbgPrint(" Timer window handle %X\n",
*(PULONG)(pWND));
// по смещению 8 в структуре tagWND лежит указатель на Win32Thread
// в этой структуре первый дворд - указатель на ETHREAD
ptCurThread = *(*(PULONG *)(pWND+8));

if(ptCurThread)
{
DbgPrint(" Timer ETHREAD %X\n",ptCurThread);

// смещение KPROCESS в ETHREAD
if(*NtBuildNumber<7100)
{

pProcess = *(PULONG)(ptCurThread+0x144);

}
else
{

pProcess = *(PULONG)(ptCurThread+0x150);

}
DbgPrint(" EPROCESS =%X\n",pProcess);

if(pProcess)
{
DbgPrint(" Process name = %s\n",
PsGetProcessImageFileName((PEPROCESS)pProcess));
}

}
}


ptsCurrent = CONTAINING_RECORD(ptsCurrent->leTimerList.Flink,
timer_struct_vistawin7,
leTimerList);

dwCount ++;

}
else
{
DbgPrint("KePrintTimers: Invalid address\n");

break;
}

}while(ptsCurrent->leTimerList.Flink!=pleTimerHead);

DbgPrint("KePrintTimers: Timers count =%d\n",dwCount);


}

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

VOID KePrintTimersXP(DWORD dwBase,DWORD dwRVA)
{
ptimer_struct_xp ptsInstalledTimers;

if(!dwBase) return;

ptsInstalledTimers = *(ptimer_struct_xp *)(dwRVA + dwBase);

DWORD dwCount = 0;

DWORD pWND;

PETHREAD ptCurThread;


while(ptsInstalledTimers)
{

if(MmIsAddressValid((PVOID)ptsInstalledTimers))

{
DbgPrint("KePrintTimers: Address valid\n");

DbgPrint("KePrintTimers: Current timer %X; Next timer %X; Timer func %X; nIDEvent %X\n",
ptsInstalledTimers,
ptsInstalledTimers->nextTimer,
ptsInstalledTimers->TimerFunc,
ptsInstalledTimers->nIDEvent);

pWND = (DWORD)ptsInstalledTimers->ptagWND;

// определение по указателю на tagWND имени процесса
if(pWND)
{

DbgPrint(" Timer window handle %X\n",
*(PULONG)(pWND));
// по смещению 8 в структуре tagWND лежит указатель на Win32Thread
// в этой структуре первый дворд - указатель на ETHREAD
ptCurThread = *(PETHREAD *)(*(PULONG)(pWND+8));

if(ptCurThread)
{
DbgPrint(" Timer ETHREAD %X\n",ptCurThread);

DbgPrint(" EPROCESS =%X\n",ptCurThread->ThreadsProcess);

if(ptCurThread->ThreadsProcess)
{
DbgPrint(" Process name = %s\n",
PsGetProcessImageFileName(ptCurThread->ThreadsProcess));
}

}
}
ptsInstalledTimers = ptsInstalledTimers->nextTimer;

dwCount ++;

}
else
{
DbgPrint("KePrintTimers: Invalid address\n");

break;
}

}

DbgPrint("KePrintTimers: Timers count =%d\n",dwCount);


}

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

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

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

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

  [C] lhc645
Категория: Разное | Добавил: MalCer (12.08.2009)
Просмотров: 829 | Комментарии: 1 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Реклама
Кабинет

Читать ЛС ()

Гость, мы рады вас видеть. Пожалуйста зарегистрируйтесь или авторизуйтесь!



Фраза дня:
Кто с нами

Сегодня были:
Статистика
Партнеры
Graffiti Decorations(R) Studio (TM) Site Promoter Mnogo-softa.net.ru-Софт,срипты,шаблоны и др. MEGA-ToP-ТОП раскрутка раскрутка сайтов. Рейтинг лучших сайтов
описание