Сообщений 7    Оценка 250 [+1/-0]         Оценить  
Система Orphus

Расширение MSGINA - это просто.

Автор: Алексей Остапенко
NetInvest
Опубликовано: 12.10.2001
Исправлено: 13.03.2005
Версия текста: 1.0.1


Что такое GINA.
Взаимодействие Winlogon и GINA
Реализация DLL-обертки
Реализация WlxInitialize.
Рабочий пример - блокировка запуска скринсейвера, если запущено приложение из списка.
Установка GINA
Отладка GINA
Заключение.

Демонстрационный проект

Что такое GINA.

GINA или Graphical Identification and Authentication dll отвечает за пользовательский интерфейс логона в системах линии Windows NT. GINA загружается и используется процессом Winlogon, отвечающим за аутентификацию пользователей. Именно она ответственна за выдачу окошек с надписями "Нажмите CTRL-ALT-DEL" и т.п. В большинстве случаев пользователю вполне достаточно возможностей стандартной реализации GINA - msgina.dll, но иногда возникают ситуации, когда нужно реализовать собственную систему логона или выполнять блокировку станции при возникновении определенного события. Именно для таких случаев программисты Microsoft оставили возможность написания собственной GINA DLL. Однако, написание собственной GINA с нуля - очень трудоемкая задача. Гораздо проще написать DLL, экспортирующую нужные по стандарту GINA функции, большинство из которых являются просто обертками над функциями уже установленной ранее GINA (например, стандартной msgina.dll). Далее я предлагаю вариант построения такой DLL.

Взаимодействие Winlogon и GINA

Winlogon оповещает GINA о возникновении какого-либо события при помощи вызова одной из экспортируемых GINA функций. То, какая функция вызывается, зависит как от самого события (нажатие CTRL-ALT-DEL или запуск скринсейвера), так и от текущего состояния Winlogon. GINA также может оповещать Winlogon о возникновении определенных событий или использовать API Winlogon для вывода диалогов взаимодействия с пользователем.

Когда Winlogon загружает GINA.DLL, он в первую очередь вызывает функцию WlxNegotiate, чтобы сообщить GINA.DLL свою версию и выяснить, какую версию Winlogon она поддерживает. Затем Winlogon вызывает функцию WlxInitialize, передавая GINA свой дескриптор, указатель на таблицу сервисных функций и имя window-станции и ожидая на выходе контекст GINA (набор данных GINA, относящихся к данной window-станции). После этого Winlogon переходит в состояние "Нет залогиненого пользователя".


В состоянии "Нет залогиненого пользователя" Winlogon ожидает прихода SAS (secure attention sequence=защищенная последовательность активации) либо от пользователя, нажавшего CTRL-ALT-DEL, либо от GINA DLL (она может генерировать свои SAS, отличные от стандартных, и оповещать Winlogon при помощи функции WlxSasNotify). В случае возникновения SAS Winlogon вызывает в GINA функцию WlxLoggedOutSAS. GINA, обработав данное событие, может либо перевести Winlogon в состояние "Есть залогиненный пользователь. Станция разблокирована", либо оставить его в текущем состоянии.

При возникновении SAS в состоянии "Есть залогиненный пользователь. Станция разблокирована" Winlogon вызывает функцию WlxLoggedOnSAS, после чего он может быть переведен в состояние "Нет залогиненого пользователя" или в состояние "Есть залогиненный пользователь. Станция заблокирована", либо оставлен в текущем состоянии.

В состоянии "Есть залогиненный пользователь. Станция заблокирована" вызывается обработчик WlxWkstaLockedSAS. В этом случае GINA может перевести Winlogon в любое из 3-х состояний (как и в предыдущем случае).

Состояния Winlogon и возможные переходы между ними схематически показаны на рисунке.

Также Winlogon следит за нажатиями клавиш и движениями мыши и при длительной неактивности пользователя вызывает в GINA функцию WlxScreenSaverNotify, позволяя GINA определить возможность запуска скринсейвера.

Если пользователь перезагружает машину, Winlogon оповещает GINA посредством вызова WlxShutdown. При этом GINA может освободить все используемые ей ресурсы.

Помимо перечисленных событий Winlogon может оповещать GINA и о других событиях (например, рестарт пользовательской оболочки), но их рассмотрение выходит за рамки этой статьи (более подробно о взаимодействии GINA DLL и Winlogon можно почитать в MSDN).

Реализация DLL-обертки

Сначала я приведу список тех функций, которые должна экспортировать GINA DLL (согласно спецификации Winlogon v1.4 для Windows XP). Их всего 21 штука (часть из них является необязательной. При отсутствии такой функции Winlogon использует свою внутреннюю реализацию. Однако, это может урезать функциональность MSGINA, поэтому мы будем экспортировать все функции):

Поскольку мы пишем обертку, то большая часть функций будет выглядеть следующим образом:

Return_Type WlxFunction(Params_List)
{
	return Wlxtbl.WlxFunction(Params_List);
}
где Wlxtbl - структура с указателями на функции из предыдущей GINA (MSGINA.DLL или другой, ранее установленной). Она выглядит следующим образом:
//таблица указателей на импортируемые функции
typedef struct _MSWlxDispatchTbl
{
    PWlxActivateUserShell WlxActivateUserShell;
    PWlxDisplayLockedNotice WlxDisplayLockedNotice;
    PWlxDisplaySASNotice WlxDisplaySASNotice;
    PWlxDisplayStatusMessage WlxDisplayStatusMessage;
    PWlxGetStatusMessage WlxGetStatusMessage;
    PWlxInitialize WlxInitialize;
    PWlxIsLockOk WlxIsLockOk;
    PWlxIsLogoffOk WlxIsLogoffOk;
    PWlxLoggedOnSAS WlxLoggedOnSAS;
    PWlxLoggedOutSAS WlxLoggedOutSAS;
    PWlxLogoff WlxLogoff;
    PWlxNegotiate WlxNegotiate;
    PWlxNetworkProviderLoad WlxNetworkProviderLoad;
    PWlxRemoveStatusMessage WlxRemoveStatusMessage;
    PWlxScreenSaverNotify WlxScreenSaverNotify;
    PWlxShutdown WlxShutdown;
    PWlxStartApplication WlxStartApplication;
    PWlxWkstaLockedSAS WlxWkstaLockedSAS;
    PWlxGetConsoleSwitchCredentials WlxGetConsoleSwitchCredentials;
    PWlxReconnectNotify WlxReconnectNotify;
    PWlxDisconnectNotify WlxDisconnectNotify;
} MSWlxDispatchTbl;
Определения типов указателей на функции смотрите внутри демонстрационного проекта

Инициализация таблицы указателей будет производиться внутри функции DllMain. В ней мы будем считывать название предыдущей GINA DLL из реестра. Его должен будет поместить туда инсталлятор, устанавливающий GINA DLL. Но, поскольку создание инсталляторов здесь не рассматривается, вам нужно будет просто скопировать содержимое значения GinaDLL из ключа

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon
в значение PreviousGina ключа
HKEY_LOCAL_MACHINE\Software\RSDN\GinaExt
если исходное значение существет (если же его нет, то эту операцию можно спокойно опустить). Затем будет производиться попытка загрузить предыдущую GINA DLL, а если это не удалось, будет загружена msgina.dll (такая последовательность необходима для обеспечения нормальной загрузки системы, если предыдущая GINA DLL будет деинсталирована).
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID)
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
            {
                //строим имя файла со списком процессов
                if(!GetModuleFileName(GetModuleHandle("ginaext.dll"),ListFileNameBuf,MaxFnameLen)) return FALSE;
                char *pos=strrchr(ListFileNameBuf,'\\');
                if(!pos) pos=ListFileNameBuf;
                else pos++;
                strcpy(pos,ListFileName);

                //читаем имя предыдущей GINA из реестра.
                HKEY hKey;
                if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,"Software\\RSDN\\GinaExt",0,KEY_QUERY_VALUE,&hKey)==ERROR_SUCCESS)
                {
                    DWORD dwType, dwLen;
                    if(RegQueryValueEx(hKey,RegValueName,NULL,&dwType,NULL,&dwLen)==ERROR_SUCCESS && dwType==REG_SZ) //получаем размер и тип значения
                    {
                        char *DllName=new char[dwLen];
                        if(RegQueryValueEx(hKey,RegValueName,NULL,NULL,(BYTE *)DllName,&dwLen)==ERROR_SUCCESS) //читаем значение
                            hGina=LoadLibrary(DllName); //пытаемся загрузить предыдущую GINA DLL
                        delete [] DllName;
                    }
                    RegCloseKey(hKey);
                }
                if(!hGina) hGina=LoadLibrary("msgina.dll"); //грузим msgina.dll, если не смогли загрузить предыдущую
                if(!hGina) return FALSE;    //полный облом, терминируемся

                //определяем адреса импортируемых функций из предыдущей GINA
                PWlxActivateUserShell *pWlxFunc=(PWlxActivateUserShell *)&Wlxtbl;
                for(int i=0;i<ImportsNumber;i++)
                {
                    pWlxFunc[i]=(PWlxActivateUserShell)::GetProcAddress(hGina,ImportFunctions[i]); //импортируем все, что сможем
                }
            }
            break;
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            FreeLibrary(hGina);
            break;
    }
    return TRUE;
}
где ImportFunctions - это массив строк с именами функций, импортируемых из msgina.dll.
ПРИМЕЧАНИЕ
Помимо инициализации таблицы функций, здесь также формируется имя некоего ресурсного файла. В этом файле будет храниться список процессов, обрабатываемых рабочим примеров. Зачем он нужен, станет понятно после того, как я расскажу, в чем же собственно будет заключаться этот пример. :)

Реализация WlxInitialize.

В отличие от простой обертки, эту функцию нам придется слегка видоизменить, т.к. нам потребуется дескриптор Winlogon и указатель на таблицу его сервисных функций:

//функция инициализации GINA
BOOL WINAPI WlxInitialize(LPWSTR lpWinsta,HANDLE hWlx,PVOID pvReserved,PVOID pWinlogonFunctions,PVOID *pWlxContext)
{
    hWinlogon=hWlx; //сохраняем хендл Винлогона для дальнейшего использования
    pWinlogonDispTbl=(PWLX_DISPATCH_VERSION_1_0)pWinlogonFunctions; //сохраняем указатель на таблицу сервисных функций Винлогона
    return Wlxtbl.WlxInitialize(lpWinsta,hWlx,pvReserved,pWinlogonFunctions,pWlxContext);
}
ПРИМЕЧАНИЕ
Заметим, что здесь использована таблица функций Winlogon версии 1.0. Это гарантирует работоспособность кода с любыми версиями Winlogon. Впрочем, тут можно было бы спокойно использовать таблицу версии 1.3 (Winlogon в Windows 2000), поскольку в дальнейшем пример использует код, не работающий на версиях NT ниже, чем Windows 2000. Однако, дополнительные функции, присутствующие в этой версии, нам не понадобятся.

Рабочий пример - блокировка запуска скринсейвера, если запущено приложение из списка.

Итак, наш пример будет блокировать запуск скринсейвера, если уже запущено одно из приложений из заданного списка (вот зачем нам нужен дополнительный файл). Вы можете сказать, что этот пример непрактичен, т.к. такого же эффекта можно добиться и другими способами, или попросту бесполезен. Насчет непрактичности сложно что-либо возразить, просто это всего лишь пример, и не более. А вот насчет полезности я мог бы и поспорить. Мне, например, уже надоело выскакивание скринсейвера, когда я смотрю дома любимый фильм в ASUS Live , а отключать каждый раз скринсейвер очень не удобно. Вот я и решил совместить приятное (написание примера) с полезным. :)

Как уже упоминалось ранее, Winlogon запрашивает у GINA возможность запуска скринсейвера посредством вызова функции WlxScreenSaverNotify. При этом GINA может либо разрешить, либо запретить запуск. Мы будем анализировать наличие в системе запущенного процесса из заданного списка и блокировать запуск, если такой процесс существует.
Список процессов перечисляется в файле ginaext.lst через пробел или любой другой разумный разделитель. Все названия нужно вводить в нижнем регистре.

Вот как выглядит моя реализация WlxScreenSaverNotify:

//функция проверки возможности запуска скринсейвера
BOOL WINAPI WlxScreenSaverNotify(PVOID pWlxContext,BOOL *pSecure)
{
    BOOL bAskGina=TRUE; //будем ли спрашивать у следующей GINA
    //открываем файл со списком процессов
    HANDLE hFile=::CreateFile(ListFileNameBuf,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if(hFile!=INVALID_HANDLE_VALUE)
    {
        DWORD dwSize=::GetFileSize(hFile,NULL);
        //мапируем его в память
        HANDLE hMap=::CreateFileMapping(hFile,NULL,PAGE_READONLY,0,dwSize,NULL);
        if(hMap)
        {
            //создаем view файла
            char *pData=(char *)::MapViewOfFile(hMap,FILE_MAP_READ,0,0,dwSize);
            if(pData)
            {
                char *pEnd=pData+dwSize;
                //получаем снимок процессов
                HANDLE hSnap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
                if(hSnap!=INVALID_HANDLE_VALUE)
                {
                    PROCESSENTRY32 pe;
                    pe.dwSize=sizeof(pe);
                    if(Process32First(hSnap,&pe))
                    do
                    {
                        char *pNull=(char *)memchr(pe.szExeFile,0,MAX_PATH); //вычисляем длину строки (если это вообще строка)
                        DWORD ProcNameLen1;
                        if(pNull && pNull>pe.szExeFile && (ProcNameLen1=pNull-pe.szExeFile-1)<dwSize) //стоит ли вообще пытаться искать такое имя процесса в файле
                        {
                            strlwr(pe.szExeFile); //приводим имя процесса к нижнему регистру
                            char *pNames=(char *)memchr(pData,pe.szExeFile[0],dwSize-ProcNameLen1); //ищем первый символ названия процесса
                            while(pNames)
                            {
                                if(!memcmp(pe.szExeFile+1,++pNames,ProcNameLen1)) //сравниваем остаток имени процесса
                                {
                                    bAskGina=FALSE; //нашли процесс из списка
                                    break;
                                }
                                int Left=pEnd-pNames-ProcNameLen1; //осталось просмотреть
                                if(Left>0) pNames=(char *)memchr(pNames,pe.szExeFile[0],Left); //опять ищем первый символ имени процесса
                                else break;
                            }
                        }
                    }
                    while(bAskGina && Process32Next(hSnap,&pe)); //пока не нашли нужный процесс и не кончились процессы
                    CloseHandle(hSnap);
                }
#if defined(_DEBUG)
                else
                {
                    pWinlogonDispTbl->WlxSwitchDesktopToWinlogon(hWinlogon);
                    pWinlogonDispTbl->WlxMessageBox(hWinlogon,NULL,L"Failed to create processes snapshot",L"Warning",MB_SYSTEMMODAL|MB_OK);
                }
#endif
                UnmapViewOfFile(pData);
            }
#if defined(_DEBUG)
            else
            {
                pWinlogonDispTbl->WlxSwitchDesktopToWinlogon(hWinlogon);
                pWinlogonDispTbl->WlxMessageBox(hWinlogon,NULL,L"Failed to create view of file",L"Warning",MB_SYSTEMMODAL|MB_OK);
            }
#endif
            CloseHandle(hMap);
        }
#if defined(_DEBUG)
        else
        {
            pWinlogonDispTbl->WlxSwitchDesktopToWinlogon(hWinlogon);
            pWinlogonDispTbl->WlxMessageBox(hWinlogon,NULL,L"Failed to create file mapping",L"Warning",MB_SYSTEMMODAL|MB_OK);
        }
#endif
        CloseHandle(hFile);
    }
#if defined(_DEBUG)
    else
    {
        WCHAR buf[1024];
        swprintf(buf,L"Failed to open file %S",ListFileNameBuf);
        pWinlogonDispTbl->WlxSwitchDesktopToWinlogon(hWinlogon);
        pWinlogonDispTbl->WlxMessageBox(hWinlogon,NULL,buf,L"Warning",MB_SYSTEMMODAL|MB_OK);
    }
#endif
    if(bAskGina) //спросим у предыдущей GINA, если не решили сами
    {
        if(Wlxtbl.WlxScreenSaverNotify) //а есть ли у чего спрашивать (функция является опциональной)?
            return Wlxtbl.WlxScreenSaverNotify(pWlxContext,pSecure);
        else //используем стандартную заглушку Winlogon
        {
            if (*pSecure)
            {
                *pSecure = WlxIsLockOk(pWlxContext);
            }
            return(TRUE);
        }
    }
    else return FALSE; //запрещаем показ скринсейвера
}
Стоит отметить, что функция WlxScreenSaverNotify является опциональной, поэтому она может не экспортироваться ранее установленной GINA DLL. В этом случае указатель Wlxtbl.WlxScreenSaverNotify будет нулевым, и попытка вызова по нему приведет к краху системы (эта проблема не может возникнуть с msgina.dll, т.к. она экспортирует все функции Wlx...). В этом случае мы используем способ, которым пользуется Winlogon при отсутствии реализации WlxScreenSaverNotify. Аналогичные проверки на допустимость указателя встроены и в функции WlxStartApplication (при этом полностью вырезана ее функциональность, т.к. при попытка нормальной реализации этой функции возникает большое количество проблем. Остается лишь надеяться, что ранее установленная GINA DLL будет экспортировать эту функцию), WlxReconnectNotify, WlxDisconnectNotify (у меня нет информации насчет обязательности реализации двух последних функций пользовательской GINA, поэтому я решил перестраховаться).

Установка GINA

Установка GINA DLL осуществляется внесением строкового значения GinaDLL в ключ:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon
Примерно так: GinaDLL="c:\ginaext.dll".

Отладка GINA

Отладка GINA DLL - сложное и утомительное занятие. Любая фатальная ошибка приводит к перезагрузке системы, что сильно увеличивает временные и моральные затраты на поиск и устранение ошибок.

Впрочем, Winlogon предоставляет вам некоторые дополнительные возможности по отладке вашей GINA в виде файла с логом событий. Для того чтобы его получить, вам нужно заменить стандартный winlogon.exe на его отладочную версию из Checked-билда или из NT DDK. После этого нужно создать запись в win.ini вида:

[WinlogonDebug]
LogFile=C:\Winlogon.log
DebugFlags=Flag1[, Flag2...]
Где флаги принимают значения: CoolSwitch, Error, Init, Notify, SAS, State, Timeout, Trace, Warn (описание смотрите в MSDN). Это даст возможность отслеживать, в каком состоянии и при каком событии произошел сбой в вашей DLL.

Если вы отлаживаете GINA на своей рабочей машине, то я бы порекомендовал также установить на нее Recovery Console (она ставится из дистрибутива Windows 2000 или Windows XP командой winnt32 /cmdcons) и разрешить копирование файлов в Recovery Console с помощью Policy Editor. Это позволит вам в крайнем случае заменить вашу неработающую GINA DLL на msgina.dll и восстановить работоспособность системы.

Заключение.

Несмотря на краткость данной статьи, я надеюсь, что она сможет облегчить нелегкий труд системных программистов по созданию собственных расширений GINA и может быть даст кому-нибудь спокойно посмотреть любимый фильм без этого назойливого скринсейвера. :)


Copyright (c) 2001, Алексей Остапенко.
    Сообщений 7    Оценка 250 [+1/-0]         Оценить