Winlogon notification package. Теорияипрактика
Роман Бурда
В операционных системах линии Windows NT диалоговая поддержка входа в систему реализована с помощью Winlogon. Он предоставляет целый ряд полезных интерфейсов. Наиболее известным является GINA (Graphical Identification and Authentication dll), который обычно используется, когда нужно изменить стандартный способ аутентификации. Например, реализовать возможность входа в систему по отпечаткам пальцев, сетчатке глаза или по электронной карточке. Менее известным, но не менее полезным является Winlogon notification package. Этот интерфейс реализует возможность обрабатывать события на вход и выход пользователей, включение и выключение операционной системы и некоторые другие. К сожалению, последний интерфейс реализован только в операционных системах Win2000 и выше.
Winlogon notification package – это dll-библиотеки, которые получают и обрабатывают события, сгенерированные Winlogon.
Logon – генерируется при входе пользователя в систему посредством Winlogon + GINA (через консоль, терминал и т. д.). Сообщения не будут приходить, если пользователь зашел посредством LogonUser.
Logoff – генерируется при выходе пользователя из системы (если он вошел в систему посредством Winlogon + GINA).
Startup – генерируется при запуске системы, а также при присоединении терминального клиента.
Shutdown – генерируется перед выключением системы, а также при отсоединении терминального клиента.
StartScreenSaver – генерируетсяпризапуске screen saver-а. В обработчике этого события запрещено показывать интерфейс пользователя.
StopScreenSaver – генерируется при остановке screen saver-а. В обработчике этого события запрещено показывать интерфейс пользователя.
Lock – генерируется, когда пользователь блокирует рабочую станцию.
Unlock – генерируется, когда пользователь разблокировал рабочую станцию, или когда системный администратор снял блокировку, заставив пользователя выйти из системы.
StartShell – генерируется после того, как пользователь зашел в систему и сетевые соединения были установлены.
Для реализации Winlogon notification package необходимо следующее.
Сначала нужно создать динамическую библиотеку, которая будет импортировать набор функций-обработчиков событий. Прототип этих функций должен строиться по шаблону:
VOID FunctionName (PWLX_NOTIFICATION_INFO pInfo); |
Как видите, вся информация о данных события хранится в структуре WLX_NOTIFICATION_INFO.
typedef struct _WLX_NOTIFICATION_INFO { ULONG Size ; ULONG Flags ; PWSTR UserName ; PWSTR Domain ; PWSTR WindowStation ; HANDLE hToken ; HDESK hDesktop ; PFNMSGECALLBACK pStatusCallback;} WLX_NOTIFICATION_INFO, * PWLX_NOTIFICATION_INFO ; |
В этой структуре:
Size – определяет размер структуры в байтах;
Flags – поле зарезервировано и должно устанавливаться в 0;
UserName – строка, определяющая имя текущего пользователя. Если событие случилось до входа пользователя в систему, то это поле равно NULL;
Domain – строка, которая определяет название домена, в который вошел текущий пользователь. Если событие случилось до входа пользователя в систему, то это поле равно NULL;
WindowStation – определяет имя оконной станции, в которой текущий пользователь работает. Если событие случилось до входа пользователя в систему, то это поле равно NULL;
hToken – дескриптор token-а пользователя. Если событие случилось до входа пользователя в систему, то это поле равно NULL;
hDesktop – дескриптор Desktop’а для текущего события;
pStatusCallback – зарезервировано для внутреннего использования.
После создания динамической библиотеки нужно указать Winlogon, где ее можно найти, и какие события она будет обрабатывать. Вся информация размещается в системном реестре в ключе
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify
Полный доступ к нему имеют только пользователи с правами локального администратора. Обыкновенные пользователи не смогут создавать или изменять подключи и/или значения.
СОВЕТСтоит отметить, что этого ключа не существует в ОС Win9x, но существует недокументированный аналог – HKEY_LOCAL_MACHINE\System\CurrentControlSet\control\MPRServices. С помощью этого ключа можно указать процессу mpr.exe (этот процес является некоторым аналогом Winlogon для Win9x, поскольку также отвечает за вход пользователей в систему) загружать в свое адресное пространство необходимую динамическую библиотеку. Таким образом, теоретически можно получить доступ к событиям входа и выхода пользователя из системы. Для указания библиотеки достаточно создать подключ с произвольным именем, и уже там указать следующие значения. ‘DLLName’ (REG_SZ)– имя динамической библиотеки или путь к ней, ‘EntryPoint’ (REG_SZ)– точка входа в библиотеку. Ею может быть название любой экспортируемой функции. ‘StackSize’ (REG_DWORD) – размер стека обычно устанавливается в 0. После перезагрузки системы указанная библиотека будет подгружена в адресное пространство mpr.exe. Поскольку в Win9x доступ к системному реестру имеют все, этот ключ довольно часто используется троянами. Ведь для них это своего рода автозагрузка. Возможно, именно поэтому Microsoft не документировала этот механизм. |
Для того чтобы подписать динамическую библиотеку на события Winlogon, необходимо создать подключ с произвольным именем, после чего указать значения в нем:
DllName (REG_EXPAND_SZ) – имя библиотеки, содержащей notification package, например Notify.dll;
Impersonate (REG_DWORD) – указывает, должен ли быть имперсонирован контекст безопасности вошедшего пользователя, когда Winlogon вызывает функцию-обработчик. (1 – должен быть имперсонирован, 0 – не должен).
Asynchronous (REG_DWORD) – указывает, должен ли процесс Winlogon создавать отдельный поток для вызова функции-обработчика (1 – создавать отдельный поток, 0 – не создавать). Фактически, это значение указывает, как будет работать Winlogon с обработчиком – асинхронно или синхронно.
Следующие значения являются необязательными. Они определяют названия функций, которые будут обрабатывать одноименные события. То есть существует возможность подписаться не на все события, а только на некоторые. Все значения имеют тип REG_SZ. В таблице 1 приведены их названия и возможные значения.
Имя значения | Пример |
Logon | WLEventLogon |
Logoff | WLEventLogoff |
Startup | WLEventStartup |
Shutdown | WLEventShutdown |
StartScreenSaver | WLEventStartScreenSaver |
StopScreenSaver | WLEventStopScreenSaver |
Lock | WLEventLock |
Unlock | WLEventUnlock |
StartShell | WLEventStartShell |
Чтобы изменения вступили в силу, нужно перегрузить компьютер. Это объясняется тем, что Winlogon загружает dll перед загрузкой системы.
Создадим в Visual Studio пример простой программы, ведущей логи сообщений Winlogon.
ПРИМЕЧАНИЕДемонстрационный проект был создан и скомпилирован в Visual Studio 6.0. |
Создадим проект Win32 Dynamic-Link Library с именем Notify. Укажем визарду разновидность проекта – A simple DLL project. Итак, создан пустой проект. Укажем используемые заголовочные файлы.
#include <Winwlx.h>#include <stdio.h> |
Первый заголовочный файл хранит в себе прототип WLX_NOTIFICATION_INFO. А второй пригодится в процессе создания логов.
У нас уже есть функция DllMain, любезно предоставленная визардом. Оставим ее без изменений. Напишем функцию создания логов. Поскольку структура WLX_NOTIFICATION_INFO содержит в себе Юникод-строки, будет удобно выводить данные в текстовый файл типа Юникод.
void WriteLog(PWSTR pStrEventName, PWLX_NOTIFICATION_INFO pInfo){ // Первый параметр функции WriteLog будет // содержать описание события, // а второй - сами данные. // Задаем название log-файлаWCHAR fName[] = L"C:\log.txt"; WCHAR buf[ 1024 ]; // Готовим строку для логов. Стоит отметить что если бы // был обьявлен макрос STRICT, вывод hDesktop // необходимо было бы делать по другому.swprintf(buf, L"Event: %s, User: %s, Domain: %s," L"Window station: %s, User token 0x%p," L"Desktop 0x%p\r\n", pStrEventName, pInfo->UserName, pInfo->Domain, pInfo->WindowStation, pInfo->hToken, pInfo->hDesktop); // Открываем log-файлдлязаписи. HANDLE hFile = CreateFileW(fName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwWritten;// Если такого файла нет, или случилась какая-то другая // ошибка, пробуем создать новый файл.if (hFile == INVALID_HANDLE_VALUE) { hFile = CreateFileW(fName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE){ // Если файл не создался, сообщаем пользователю // системным звуком о возникновении ошибки.::MessageBeep(MB_ICONASTERISK); return; } WCHAR data = 0xFEFF;// Указываем, что созданный файл будет // включать текст типа Юникод.if (!WriteFile(hFile, &data, sizeof(data), &dwWritten, NULL)) ::MessageBeep(MB_ICONASTERISK);} //Поскольку открытый файл может быть не пустым, //переходимвегоконец if (SetFilePointer(hFile, 0, NULL, FILE_END) == INVALID_SET_FILE_POINTER) { ::MessageBeep(MB_ICONASTERISK);return; } //Записываем строку лога в файлif (!WriteFile(hFile, buf, wcslen(buf) * sizeof(WCHAR), &dwWritten, NULL)) ::MessageBeep(MB_ICONASTERISK);//Заканчиваем работу с файлом CloseHandle(hFile);} |
Теперь осталось написать функции-обработчики событий. Ради удобства назовем их так же, как показано в таблице 1.
extern "C" void __stdcall WLEventLogon(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"user logon", pInfo);}extern "C" void __stdcall WLEventLogoff(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"user logoff", pInfo);}extern "C" void __stdcall WLEventStartup(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"system startup", pInfo);}extern "C" void __stdcall WLEventShutdown(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"system shutdown", pInfo);}extern "C" void __stdcall WLEventStartScreenSaver(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"screen saver started", pInfo);}extern "C" void __stdcall WLEventStopScreenSaver(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"screen saver stopped", pInfo);}extern "C" void __stdcall WLEventLock(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"Workstation locked", pInfo);}extern "C" void __stdcall WLEventUnlock(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"Workstation unlocked", pInfo);}extern "C" void __stdcall WLEventStartShell(PWLX_NOTIFICATION_INFO pInfo){ WriteLog(L"User already loged on and network" L" resouces are avaliable", pInfo);} |
Теперь необходимо экспортировать эти функции для других приложений. Для этого создадим файл Notify.def и добавим его в проект. В единственной секции EXPORTS укажем экспортируемые функции
EXPORTS WLEventLogon WLEventLogoff WLEventStartup WLEventShutdown WLEventStartScreenSaverWLEventStopScreenSaver WLEventLock WLEventUnlock WLEventStartShell |
После успешной компиляции необходимо подписаться на события Winlogon.