Что такое GINAи зачем ее заменять?
GINA - это часть WinLogon, которую можно заменять для модификации функциональности или UI входа в Windows. Заменив GINA, можно выбрать механизм аутентификации, используемый Windows для интерактивных пользователей. Зачастую это полезно при регистрации с применением смарт-карт или биометрических данных.
Честно скажу, замена GINA - сложная задача и подходить к ней надо серьезно. К такой замене следует прибегать лишь в том случае, если другого варианта нет (например при реализации нового механизма регистрации, не поддерживаемого Windows).
На секунду задумайтесь о том, что делает GINA. Он собирает учетные данные локальных и удаленных пользователей (последних через Terminal Services), которым требуется интерактивная регистрация на компьютере. Затем компонент устанавливает сеанс входа. Его взлом позволит украсть открытые пароли, биометрические данные пользователя, PIN-коды смарт-карт и т. д. Скомпрометированный компонент GINA может служить "черным ходом", позволяя любому воспользоваться привилегиями администратора. Короче говоря, заменяя GINA, поручите эту работу лучшим программистам и тщательно проверьте результат.
И еще кое-что. Здесь тот самый случай, когда чем проще и меньше, тем лучше. Этот компонент работает под учетной записью SYSTEM, принимает данные от локальных и удаленных пользователей и выполняется постоянно. Поэтому его код должен быть абсолютно пуленепробиваемым.
С чего начать
На момент написания этой статьи документация по реализации GINA была очень скудной. В Platform SDK есть два примера: GINASTUB и GINAHOOK. GINASTUB просто загружает MSGINA.DLL, реализацию GINA по умолчанию, входящую в состав Windows, и переправляет ей все запросы. При сборке и установке этого примера вы не увидите никаких изменений в системе. Так как GINASTUB обертывает MSGINA, она может выполнять пре- и пост-процессную обработку каждого запроса. Возможно, вам хватит этого несложного метода, но он безусловно не годится, если надо сделать нечто нетривиальное вроде замены механизма регистрации на биометрическую аутентификацию.
Второй пример называется GINAHOOK. Он похож на GINASTUB, но более продвинутый, перехватывая отображаемые MSGINA диалоги. Предлагаемая методика позволяет изменить внешний вид любого диалогового окна входа и в какой-то мере модифицировать его поведение. Однако опасность тут в том, что это приведет к использованию недокументированной функциональности MSGINA. К такой функциональности относятся как простые вещи вроде идентификаторов элементов управления в диалогах, так и поведение нижележащих оконных процедур. Не идите по этому пути, если требуется поддерживать разные версии операционной системы, потому что ваша реализация GINA может рухнуть при установке очередного пакета обновлений!
Большинство нетривиальных реализаций GINA лучше всего писать с нуля. Но на воле такие звери не встречаются (и уж точно ни одного бесплатного примера). Вот я и исправлю эту ситуацию.
Из-за сложности предмета я посвящу ему несколько выпусков своей рубрики и не смогу осветить все темные уголки разработки GINA. Моя цель - дать вам начальный импульс на случай, если вам придется самостоятельно разрабатывать GINA. Дополнительную информацию вы найдете на странице Wikki, посвященной разработке GINA, где можно взять (или добавить) разнообразные сведения и исходный код. Зайдите на мою страницу Wikki по ссылке pluralsight.com/wiki.
SAS
Прежде чем углубиться в тему, я хотел бы рассказать о SAS (Secure Attention Sequence). Это действие, выполняемое пользователем для привлечения внимания операционной системы и позволяющее ему проводить некоторые операции, связанные с безопасностью, например вход, разблокирование рабочей станции или смену пароля.
Наиболее известная SAS - комбинация клавиш Ctrl+Alt+Del, перехватываемая ядром. Она помогает помешать вредоносным программам, пытающимся подменить диалог входа и украсть учетные данные пользователя, потому что WinLogon, получая SAS-уведомление, изменяет обычный рабочий стол пользователя на защищенный до запроса учетных данных. Интересовались, куда деваются ваши панель задач и рабочий стол при нажатии Ctrl+Alt+Del? Они никуда не исчезают, просто их нет на текущем рабочем столе. Цель всего этого - приучить пользователей вводить пароль лишь после нажатия Ctrl+Alt+Del.
Но SAS - это не только Ctrl+Alt+Del. Например, если биометрическое устройство способно сообщать драйверу о том, что оно используется, это может служить SAS (скажем, пользователь поднес палец к сканеру отпечатков пальцев). Часто SAS вызывается устройством чтения смарт-карт, когда такая карта вынимается или вставляется. При создании собственного GINA нужно выбрать механизм(ы) вызова SAS. Функ-ция WlxSasNotify в WinLogon вызывается при обнаружении SAS или при необходимости имитировать его. Конечно, можно заставить WinLogon использовать Ctrl+Alt+Del, если нет ничего более подходящего.
Структура GINA
GINA представляет собой DLL, загружаемую и периодически вызываемую WinLogon. Это "долгоиграющая" библиотека, обычно выгружаемая только при перезагрузке компьютера, так что утечки памяти крайне нежелательны!
Нестандартный компонент GINA должен содержать несколько точек входа, определенных Microsoft. Я перечислил эти функции в табл. 1. Такой компонент практически всегда поддерживает внутреннее состояние, и WinLogon помогает в этом, передавая void* первым аргументом этих функций. Вам решать, на что указывает этот указатель. В моем примере это указатель на экземпляр класса Gina, реализующего состояние и поведение моего компонента GINA.
Новичков в разработке GINA смущает то, что помимо Wlx-функций, которые должен реализовать нестандартный GINA, WinLogon предоставляет несколько функций, которые GINA может вызывать при необходимости (табл. 2). Так как у этих функций одинаковый префикс Wlx, их легко перепутать. Быстро различить их можно по первому параметру функции. Если это HANDLE hWlx, значит, функция предоставляется WinLogon для вызова из GINA, а если PVOID pWlxContext, эту функцию должен реализовать GINA.
Состояния WinLogon
В любой момент WinLogon может пребывать в одном из трех состояний; в этой статье они обозначаются при каждом упоминании. Возможные состояния таковы: LOGGED_OFF (WinLogon в настоящее время не зарегистрировал пользователя), LOGGED_ON (пользователь вошел) и LOCKED (пользователь зарегистрирован на заблокированной рабочей станции).
WinLogon вызывает некоторые важные функции GINA при переходе из одного состояния в другое. Теперь, когда вы знаете о SAS и состояниях WinLogon, я продемонстрирую GINA в действии.
На рис. 1 приведена упрощенная схема состояний и переходов GINA, заглядывайте туда при чтении статьи. Она включает многие основные функции GINA, которые вызывает WinLogon, и иллюстрирует нормальный ход событий.
Инициализация
При загрузке компьютера после инициализации операционной системы и запуска всех автоматических сервисов WinLogon загружает GINA и вызывает WlxNegotiate, затем WlxInitialize.
Реализация WlxNegotiate тривиальна. Она просто позволяет GINA и WinLogon сверить версии и убедиться, что все пойдет гладко. Выбранная версия определяет функциональность, ожидаемую WinLogon от вашего GINA, а также функциональность, ожидаемую GINA от WinLogon. На момент написания этой статьи последней версией была 1.4; она обеспечивает дополнительную поддержку Remote Desktop в Windows XP, так что именно ее я реализую в примере FULLGINA.
В функции WlxInitialize вы получаете описатель (hWlx), соответствующий WinLogon, а также указатель на таблицу функций, которые может вызывать GINA. Это Wlx-функции, реализованные в WinLogon и принимающие HANDLE первым аргументом. Чтобы впоследствии вызывать эти функции, потребуется где-то сохранить указатель и описатель. Вот здесь-то и понадобится последний аргумент pWlxContext, который является выходным параметром, возвращаемым WinLogon. Проще всего заставить его указывать на структуру данных (class или struct), используемую для хранения состояния GINA, в котором содержится описатель и указатель на таблицу функций, переданные WinLogon. На рис. 2 приведен очень простой пример.
Настоящая реализация в моем примере сложнее, но идея показана верно. Отныне при вызове любых функций GINA из WinLogon, последний передает pWlxContext первым параметром, так что у вас всегда есть доступ к состоянию. Например:
BOOL WINAPI WlxIsLogoffOk(PVOID pWlxContext) { return ((Gina*)pWlxContext)->IsLogoffOk(); }
Просто переправляя каждый вызов классу GINA, я могу добавлять переменные-члены класса, хранящие состояния GINA. Это естественный способ разработки GINA, а заглушки можно повторно использовать в любом проекте.
Один день из жизни GINA
После инициализации GINA WinLogon запускает свой автомат состояний, вызывая функции, экспортированные из GINA, по мере изменения своего состояния. Сразу после инициализации пользователь не зарегистрирован, так что WinLogon требует вывести приглашение, вызывая WlxDisplaySASNotice. На этом этапе обычный GINA выводит знакомый диалог с текстом "Press Ctrl+Alt+Del to log in" ("Нажмите Ctrl+Alt+Del для входа").
Когда пользователь нажимает Ctrl+Alt+Del или когда GINA вызывает WlxSasNotify для генерации SAS другого типа, WinLogon уничтожает диалоговое окно (подробности позже) и вызывает следующую функцию GINA - WlxLoggedOutSAS.
Имя WlxLoggedOutSAS может показаться забавным, но оно точно отражает происходящее в GINA. Вы пребываете в состоянии LOGGED_OUT и получаете SAS. Теперь нужно аутентифицировать пользователя, пытающегося зарегистрироваться. Мой пример FULLGINA открывает диалоговое окно для получения имени пользователи и пароля, а затем вызывает LsaLogonUser для проверки правильности пароля. Если пароль неправильный, я вывожу сообщение об ошибке и снова запрашиваю имя пользователя и пароль. Получив правильные учетные данные, LsaLogonUser устанавливает сеанс входа для пользователя и возвращает описатель маркера (token) в мой код, в свою очередь возвращаемый в WinLogon.
WinLogon принимает маркер от GINA и настраивает список управления доступом (ACL) на рабочем столе по умолчанию, закрывая его от всех, кроме сеанса входа текущего пользователя. Помимо администраторов и самой операционной системы другие зарегистрированные пользователи не могут обращаться к этому рабочему столу. Когда рабочий стол по умолчанию готов, WinLogon снова вызывает GINA через функцию WlxActivateUserShell. Теперь GINA должен запустить пользовательскую оболочку и вернуть управление WinLogon.
Интересно, что WinLogon не предоставляет функции WlxActivateUserShell описатель маркера, переданный на предыдущем этапе. Это состояние GINA должен поддерживать самостоятельно. Теперь WinLogon находится в состоянии LOGGED_ON и всем управляет зарегистрированный пользователь. Допустим, пользователь нажимает Ctrl+Alt+Del. Можете угадать имя вызываемой WinLogon функции?
Если вы выбрали WlxLoggedOnSAS, вы угадали. Здесь пользователю, вероятно, стоит предложить несколько вариантов. На рис. 3 приведен обычный диалог, отображаемый на этом этапе. Он выглядит знакомым. На самом деле реализовать его куда легче, чем кажется, благодаря тому, что WinLogon обеспечивает большую часть функций. От GINA требуется лишь сообщить WinLogon, какой вариант выбрал пользователь, возвратив одну из нескольких предопределенных констант из WlxLoggedOnSAS. Обработка запросов на изменение пароля - единственное, что требует от программиста существенных усилий, и если пароли используются, эта функциональность все равно потребуется в других местах GINA.
Допустим, пользователь решил заблокировать компьютер. Тогда WinLogon вызовет GINA-функцию WlxDisplayLockedNotice, аналогичную WlxDisplaySAS-Notice. Вы просто отображаете модальный диалог и ждете, пока WinLogon уничтожит его.
Когда пользователь нажимает Ctrl+Alt+Del, WinLogon вызывает WlxWkstaLockedSAS, и вы снова должны запросить у пользователя учетные данные. Сложность здесь в том, что GINA должен сообщить WinLogon, тот ли это пользователь, который заблокировал компьютер и теперь вернулся разблокировать его или кто-то совершенно другой. В последнем случае вы проверяете, является ли новый пользователь администратором, и спрашиваете, желает ли он принудительно завершить сеанс текущего пользователя. В этом случае рано или поздно будет вызвана WlxLogoff, которая вернет управление в WlxDisplaySASNotify для запроса учетных данных пользователя.
Если пользователь просто разблокирует компьютер, WinLogon возвращается в состояние LOGGED_ON и пользователь снова управляет рабочим столом. Теперь не забудьте, что пользователь может завершить сеанс, выключить компьютер или даже заблокировать компьютер, не нажимая Ctrl+Alt+Del и не попадая в GINA. Например, API-функции LockWorkstation и даже ExitWindowsEx можно вызвать из любого приложения, в том числе из Windows Explorer. Конечно, GINA будет уведомлен о том, что пользователь завершает сеанс или блокирует компьютер, но учитывайте, что не все эти действия инициируется GINA.
Модальные диалоги и потоки GINA
Каждый модальный диалог, отображаемый из GINA, должен поддерживать прерывание из WinLogon. Есть несколько причин, по которым WinLogon может вмешаться в пользовательский интерфейс GINA: пользователь может быть неактивен в течение нескольких минут, может появиться SAS, сработать хранитель экрана и т. д. WinLogon обходится с этим очень просто. Вместо того чтобы вызывать обычные Win32-функции DialogBox, DialogBoxParam и др., GINA вызывает аналогичные функции, раскрываемые WinLogon. Последний может перехватить процедуру диалогового окна и закрыть модальный диалог в любой момент. Так что, если для реализации диалоговых окон вы используете среду вроде MFC, вам придется слегка подкрутить ее так, чтобы вызывались API-функции WinLogon.
Но при использовании MFC или более сложных сред встает вопрос об их пригодности для разработки GINA. Не забудьте, хороший GINA компактен, прост и пуленепробиваем. Постарайтесь свести объем загружаемого GINA кода к минимуму.
Раз уж я заговорил об интерфейсе, хотелось бы обратить ваше внимание на то, что GINA - на самом деле GUI-компонент. Он спроектирован однопоточным, по крайней мере с точки зрения WinLogon. Последний не предполагает вызовов из других потоков помимо того, из которого GINA вызывался в первый раз. Например, когда GINA получает SAS от внешнего устрой-ства, ему не следует вызывать WlxSaSNotify из случайного потока. Вместо этого нужно использовать поток WinLogon. Лучше всего это сделать из потока, вызвавшего WlxDisplaySAS/LockedNotice, чтобы дать понять WinLogon, что пользователь сгенерировал SAS, обратившись к вашему устройству, и хочет войти в систему или заблокировать ее. Если у вас есть второй поток, принимающий данные от устройства, можно просто отправить сообщение диалогу, отображающему SAS или уведомление о блокировке. Затем UI-поток может смело вызывать WlxSasNotify.
Развертывание GINA
После того как GINA собран, его необходимо развернуть и протестировать. Я настоятельно не рекомендую развертывать GINA на компьютере разработчика. Используйте отдельный компьютер для тестирования GINA. Лично я применяю Virtual PC и ее функцию отмены дисковых операций. Если случится что-то дей-ствительно плохое и все закончится бесконечным циклом перезагрузок (да, рано или поздно с вами это произойдет), я просто закрываю Virtual PC и отменяю изменения. Другой вариант - выбрать безопасный режим и удалить неправильный GINA.
Для развертывания GINA в тестовых целях просто скопируйте DLL на целевой компьютер (для нее подойдет каталог System32) и измените реестр, добавив именованный параметр в раздел WinLogon:
HKLMSOFTWAREMicrosoftWindows NTCurrentVersionWinLogon
Именованный параметр должен называться GinaDLL, иметь тип REG_SZ, а значением должно быть имя DLL. Перезагрузите компьютер, и новый GINA запустится при загрузке компьютера. При попытке развернуть новую версию GINA текущая версия может оказаться заблокированной ОС, и ее файл не удастся перезаписать. Тогда переименуйте файл с GINA на диске до копирования нового. Впоследствии вы сможете просто удалить старый файл.
Если случилась неприятность и вам требуется восстановить старый GINA, просто удалите ключ GinaDLL и не забудьте, что к реестру можно обращаться удаленно при условии, что вы являетесь администратором на удаленном компьютере. Просто запустите REGEDIT.EXE и выберите File / Connect Network Registry.
Завершив тестирование и выполнив реальное развертывание, вы должны после копирования GINA DLL на диск пользователя установить для файла ACL так, чтобы лишь администраторы могли изменять файл. Вы не достигнете такого уровня защиты по умолчанию, просто поместив файл в каталог System32.
Отладка GINA
Если вы не относитесь к тем, кто регулярно использует низкоуровневые символьные отладчики, отладка GINA в период его выполнения в WinLogon не доставит вам удовольствия. Вот почему мой пример спроектирован так, чтобы он мог работать без WinLogon, и я рекомендую вам сделать то же самое. Я просто добавляю в GINA DLL еще одну входную точку, DebugGINA. В окончательных сборках эта функция ничего не делает. Однако в отладочных сборках через эту функцию я могу задействовать любой сценарий отладки. Для этого надо сделать интерфейс абстрактным с точки зрения WinLogon, так чтобы при отладке можно было имитировать таблицу функций WinLogon, подставляя соб-ственную реализацию. Стандартный способ добиться этого при компонентном тестировании - применение интерфейсов в сочетании с суррогатными объектами.
Я использую очень простой интерфейс IWinLogon, содержащий методы для всех функций, которые GINA требуется вызывать из WinLogon. Например, WlxDialogBox превращается в IWinLogon::wlxDialogBox. А при обычной работе я использую реализацию IWinLogon, дейст-вительно вызывающую функции из WinLogon. При отладке подставляется суррогатная реализация, во многих случаях ничего не делающая. Суррогатная реализация IWinLogon::wlxDialogBox просто вызывает DialogBox. Элементарно, но работает как часы.
Конечно, вам понадобится программа для загрузки GINA и вызова отладочной функции, но это нетрудно. Вот простой фрагмент кода, вызывающий LoadLibrary и GetProcAddress для вызова отладочной версии GINA. Просто скомпилируйте его в EXE-файл и сможете войти в GINA из отладчика.
void main() { GetProcAddress(LoadLibrary("mygina.dll"), "DebugGINA")(); }
Но сначала убедитесь, что отладка выполняется под учетной записью SYSTEM для имитации среды Win-Logon. При частой отладке GINA я держу открытой командную строку под учетной записью SYSTEM и просто ввожу DEVENV для запуска отладчика, загрузки проекта и трассировки.
Как получить командную строку под учетной записью SYSTEM? Один из способов - назначить запуск по расписанию интерактивного задания, указав CMD.EXE в качестве приложения:
at 7:32pm /interactive cmd
Конечно, выбранное время должно быть в ближайшем будущем, например через одну минуту. Это заставляет планировщик (работающий под SYSTEM) запустить командную строку, наследующую контекст защиты SYSTEM. Отладчик, запущенный из такой командной строки, тоже унаследует этот контекст. Командную строку следует назвать примерно так (наберите в командной строке после ее запуска):
title SYSTEM (DANGER, WILL ROBINSON)
Этот глуповато выглядящий заголовок на самом деле очень серьезен - командную строку нужно закрывать по окончании отладки, так как SYSTEM имеет полный контроль над всем на компьютере и опечатка в такой команд-ной строке может иметь серьезные последствия.
В следующем выпуске этой рубрики я рассмотрю некоторые более сложные аспекты разработки GINA вроде применения CreateProcessAsUser для запуска оболочки и другие важные вопросы. В конце серии у вас будет простая, но полнофункциональная реализация GINA, подходящая для того, чтобы начать с нее свою работу.
Исходный код можно скачать по ссылке: http://msdn.microsoft.com/msdnmag/code05.aspx.
Вопросы и комментарии (на английском языке) присылайте по адресу briefs@microsoft.com.
Кит Браун (Keith Brown) - соучредитель Pluralsight, специализируется на разработке высококачественных курсов обучения для программистов. Его последняя книга "The .NET Developer"s Guide to Windows Security" доступна по ссылке www.pluralsight.com/keith/book. С ним можно связаться через www.pluralsight.com/keith. |