Исследование подсистемы GUI: горячие клавиши в Windows — Архив WASM.RU

Все статьи

Исследование подсистемы GUI: горячие клавиши в Windows — Архив WASM.RU

В Операционной Системе Windows любая программа может зарегистрировать себе комбинацию горячих клавиш. Для этого существуют следующие функции:

BOOL RegisterHotKey(      
    HWND hWnd,
    int id,
    UINT fsModifiers,
    UINT vk
);

И соответственно

BOOL UnregisterHotKey(      
    HWND hWnd,
    int id
);

для удаления горячей клавиши.

Например, Winlogon.exe при входе в систему регистрирует себе комбинацию клавиш CTRL+ALT+DEL .

В этой статье мы рассмотрим механизм регистрации горячих клавиш. Как уже упоминалось выше для того чтобы зарегистрировать себе горячие клавиши, программе достаточно вызвать функцию RegisterHotKey() из user32.dll.

Дизассемблирование этой функции показало, что она вызывает системный сервис NtUserRegisterHotKey()

.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. Вот ее формат

typedef struct tagHOTKEY {
    PTHREADINFO pti;
    struct tagWND *spwnd;
    UINT    fsModifiers;
    UINT    vk;
    int     id;
    struct tagHOTKEY *phkNext;
} HOTKEY, *PHOTKEY;

После всех операций по заполнению структуры она помешается в список.

.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.

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:

    ; 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.

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;
};

Ну и собственно функция которая выводит на отладочную печать все горячие клавиши зарегистрированные в системе .

//
// 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 за оказание большой помощи в написании данного материала.

В приложении к статье вы найдете исходные коды драйвера и приложения.

2002-2013 (c) wasm.ru