Исследование подсистемы GUI: горячие клавиши в Windows — Архив WASM.RU
В Операционной Системе Windows любая программа может зарегистрировать себе комбинацию горячих клавиш. Для этого существуют следующие функции:
Код (Text):
BOOL RegisterHotKey( HWND hWnd, int id, UINT fsModifiers, UINT vk );И соответственно
Код (Text):
BOOL UnregisterHotKey( HWND hWnd, int id );для удаления горячей клавиши.
Например, Winlogon.exe при входе в систему регистрирует себе комбинацию клавиш CTRL+ALT+DEL .
В этой статье мы рассмотрим механизм регистрации горячих клавиш. Как уже упоминалось выше для того чтобы зарегистрировать себе горячие клавиши, программе достаточно вызвать функцию RegisterHotKey() из user32.dll.
Дизассемблирование этой функции показало, что она вызывает системный сервис NtUserRegisterHotKey()
Код (Text):
.text:7E378419 RegisterHotKey proc near .text:7E378419 .text:7E378419 hWnd = dword ptr 4 .text:7E378419 id = dword ptr 8 .text:7E378419 fsModifiers = dword ptr 0Ch .text:7E378419 vk = dword ptr 10h .text:7E378419 .text:7E378419 mov eax, 11EAh .text:7E37841E mov edx, 7FFE0300h .text:7E378423 call dword ptr [edx] .text:7E378425 retn 10h .text:7E378425 RegisterHotKey endpВ свою очередь, сервис NtUserRegisterHotKey из win32k.sys вызывает внутреннюю функцию __RegisterHotKey, которая выполняет ряд очень важных проверок. Среди которых есть проверка на повторную регистрацию комбинации клавиш, за это отвечает также не экспортируемая функция __FindHotKey.
Далее заполняется структура tagHOTKEY. Вот ее формат
Код (Text):
typedef struct tagHOTKEY { PTHREADINFO pti; struct tagWND *spwnd; UINT fsModifiers; UINT vk; int id; struct tagHOTKEY *phkNext; } HOTKEY, *PHOTKEY;После всех операций по заполнению структуры она помешается в список.
Код (Text):
.text:BF8B78BC mov eax, _gphkFirst .text:BF8B78C1 mov [esi+14h], eax .text:BF8B78C4 mov _gphkFirst, esi_gphkFirst - это не экспортируемая переменная в которой хранится адрес первой структуры HOTKEY(все они связанны между собой полем phkNext ).
Таким образом, если мы получим эту переменную, мы сможем спокойно манипулировать всеми горячими клавишами в системе.
Однако, искать _gphkFirst в NtUserRegisterHotKey, довольно неудобный способ. Лучше реализовать через поиск внутренней функции __UnregisterHotKey которую вызывает сервис NtUserUnregisterHotKey. В __UnregisterHotKey, в свою очередь, найти вызов функции _FindHotKey в которой почти в самом начале в регистр esi помещается адрес _gphkFirst.
Код (Text):
76A _FindHotKey@28 proc near ; CODE XREF: _UnregisterHotKey(x,x)+1Bp .text:BF8B776A ; _RegisterHotKey(x,x,x,x)+81p .text:BF8B776A .text:BF8B776A arg_0 = dword ptr 8 .text:BF8B776A arg_4 = dword ptr 0Ch .text:BF8B776A arg_8 = dword ptr 10h .text:BF8B776A arg_C = word ptr 14h .text:BF8B776A arg_10 = dword ptr 18h .text:BF8B776A arg_14 = dword ptr 1Ch .text:BF8B776A arg_18 = dword ptr 20h .text:BF8B776A .text:BF8B776A ; FUNCTION CHUNK AT .text:BF8B7716 SIZE 0000004F BYTES .text:BF8B776A .text:BF8B776A mov edi, edi .text:BF8B776C push ebp .text:BF8B776D mov ebp, esp .text:BF8B776F mov ecx, [ebp+arg_18] .text:BF8B7772 and dword ptr [ecx], 0 .text:BF8B7775 and [ebp+arg_18], 0 .text:BF8B7779 push ebx .text:BF8B777A push esi .text:BF8B777B mov esi, _gphkFirstАлгоритм поиска выглядит следующим образом:
- 1) Ищем номер сервиса NtUserUnregisterHotKey. Определить его можно через функцию UnregisterHotKey из user32.dll:
Код (Text):
; UnregisterHotKey ; BOOL __stdcall NtUserUnregisterHotKey(HWND hWnd, int id) public _NtUserUnregisterHotKey@8 _NtUserUnregisterHotKey@8 proc near hWnd= dword ptr 4 id= dword ptr 8 mov eax, 1240h mov edx, 7FFE0300h call dword ptr [edx] retn 8 _NtUserUnregisterHotKey@8 end- 3) Передаем номер сервиса драйверу
- 4) В драйвере получаем адрес NtUserUnregisterHotKey.
- 5) Дизассемблируем NtUserUnregisterHotKey. Ищем третий call. Третий call - это вызов _UnregisterHotKey.
- 6) Дизассемблируем_ UnregisterHotKey и ищем первый call. первый call это вызов __FindHotKey.
- 7) Дизассемблируем FindHotKey. Ищем уже искомую инструкцию mov esi, адрес_переменной.
В качестве дизассемблера длин используется VirXasm32.
После того, как адрес получен, настало время разобраться со структурой HOTKEY до конца. Нас будет интересовать ее поле PTHREADINFO pti. Опытным путем было установлено, что первый член THREADINFО является структура _W32THREAD.
Код (Text):
struct _W32THREAD { /*+0x0*/ struct _ETHREAD* pEThread; /*+0x4*/ unsigned long RefCount; /*+0x8*/ struct _TL* ptlW32; /*+0xС*/ void* pgdiDcattr; /*0x10*/ void* pgdiBrushAttr; /*+0x14*/ void* pUMPDObjs; /*+0x18*/ void* pUMPDHeap; /*+0x1c>*/ unsigned long dwEngAcquireCount; /*+0x20>*/ void* pSemTable; /*+0x24>*/ void* pUMPDObj; };Ну и собственно функция которая выводит на отладочную печать все горячие клавиши зарегистрированные в системе .
Код (Text):
// // DumpHotKeys // VOID DumpHotKeys( IN PVOID gphkFirst ) { ULONG dwAddr; KAPC_STATE ApcState; PETHREAD pThread; PEPROCESS pProc; PHOTKEY phk; // // we must do it in process context where win32k.sys is mapped // KeStackAttachProcess( pExpEprocess , &ApcState ); dwAddr = *(PULONG)gphkFirst; KeUnstackDetachProcess(&ApcState); phk = (PHOTKEY)dwAddr; // // parse all registred hot keys // while( phk != NULL ) { pThread = *(PULONG)phk->pti; pProc = *(PULONG)( (ULONG)pThread + 0x220 ); KdPrint(("Process Name : %s\n" , (ULONG)pProc + 0x174 )); KdPrint(("id : %d\n" , phk->id )); KdPrint(("Combination : %s + %X\n" , GetButton( phk->fsModifiers ) , phk->vk )); KdPrint(("------------------------------------------\n")); phk = phk->phkNext; } }Результат работы программы можно увидеть на скрине:
![]()
Хочется отметить, что в Висте механизм регистрации горячих клавиш немного изменен.
А так же хотелось бы выразить благодарность 0x0c0de за оказание большой помощи в написании данного материала.
В приложении к статье вы найдете исходные коды драйвера и приложения. © Sav1or
Исследование подсистемы GUI: горячие клавиши в Windows
Дата публикации 31 май 2009