войти зарегистрироваться

Windows whois

индекс
152,43

Вход в систему по подключению определенной флешки

Одним ужасным пятничным вечером мне стало интересно как реализован вход в систему(Windows 7) по отпечатку пальца, так часто использующийся на ноутбуках. Что меня большего всего интересовало – как сделана такая прозрачная интеграция с WinLogon(механизм входа в систему).

С помощью знакомого я узнал что сие называется Credential Provider(по крайней мере начиная с Vista, до неё – был другой механизм). И тут-то я вспомнил что давно хотел сделать чтобы система разблокировалась при подключении одной определенной флешки. Поэтому я захотел быстренько наваять такой проект.

Первое что я сделал – это поискал примеры реализации Credential Provider. Они быстро нашлись в Windows SDK, а так же отдельно примеры(но устаревшие – для висты).

Среди примеров был наиболее близкий мне – SampleHardwareEventCredentialProvider.

Расскажу вкратце как устроен механизм провайдеров учетных записей(Credential Provider).

В реестре(HKLM\Software\Microsoft Windows\CurrentVersion\Authentification) есть две ветки – собственно Credential Providers и Credential Provider Filters. В каждом из них набор вида “ветка-GUID”(GUID – уникальный идентификатор) с дефолтным параметром – названием провайдера или фильтра. Тут надо пояснить что такое провайдеры и фильтры.

Провайдер – он дает нам возможность входа в систему. Например по отпечатку пальца, по вводу пароля(дефолтное поведение), по смарткарте, и т.д.

Фильтры – фильтруют “лишнее” поведение от пользователя. Пример – если политикой безопасности запрещен вход по смарткарте – фильтр может отключить такой провайдер.

Далее, в реестре в всем известном страшном месте HKCR\CLSID описаны GUIDы, а в нашем случае – для идентификаторов фильтров и провайдеров прописаны их названия, название файла — dll-ки, и модель потоков(ThreadingModel=Apartment).

Фильтры мы рассматривать не будем, перейдем конкретно к провайдеру.

В проекте у нас есть несколько классов:
CSampleProvider, реализующий интерфейс ICredentialProvider. Именно он отвечает за механизм входа и предоставление  удостоверения(Credential)
CSampleCredential, реализующий труднопроизносимый интерфейс ICredentialProviderCredential. Собственно тут указаны имя пользователя, пароль(или токен авторизации, что лучше). Экземпляр этого класса мы отдаем WinLogon для входа в систему.
CommandWindow, класс окна-заглушки.

По умолчанию в SampleHardwareEventCredentialProvider такое поведение:
При создании главного класса механизмом входа WinLogon – в другом потоке создается окно с кнопкой, которая симулирует событие подключения устройства. При нажатии на кнопку – происходит переход на логин с учетной записью “Administrator”.

Что мне надо минимально поменять из этого поведения чтобы было рабочее решение для меня?
Скрыть окно, сменить жёстко прописанную учетную запись “Administrator” на мою(с автовводом пароля), включить автовход, и реализовать событие WM_DEVICECHANGE.

Скрыть окно было просто – всего лишь найти ShowWindow(hWnd, SW_SHOW); и заменить на SW_HIDE
Сменить жёстко прописанную учетную запись – тоже было просто – я создал отдельный файл consts.h, где прописал:

static const wchar_t* USERNAME = (L"Кирилл Орлов");
static const wchar_t* PASSWORD = (L"Сорок тысяч обезьян в жопу сунули банан.");

* This source code was highlighted with Source Code Highlighter.


Включить автовход тоже было несложно:

HRESULT CSampleCredential::SetSelected(__out BOOL* pbAutoLogon) 
{
  *pbAutoLogon = TRUE; //тут было FALSE
  return S_OK;
}

* This source code was highlighted with Source Code Highlighter.


Этот метод вызывается при выборе пользователя для входа.

Осталось единственное сложное – реализовать событие WM_DEVICECHANGE.
В нем мне надо найти некий уникальный идентификатор флешки, сравнить с эталонным, и в случае совпадения – поставить флаг что нужное устройство подключено, который затем прочитает CSampleCredential.

Что может быть уникальным у флешки? Вобщем-то много всего, VendorID/ProductID(уникальны на продукт, т.е. у одной и той же серии флешек – совпадает), номер серийника раздела(сбрасывается при переформатировании). Я сравниваю PNPID через механизм WMI(Windows Management Instrumentation).

Вообще про WMI можно говорить долго,  скажу только что это средство получения информации и управления кучей компонентов ОС и не только, и имеет свой SQL-подобный язык запросов, называемый WQL. Есть замечательная утилита от Microsoft под названием WMI Browser, очень советую поставить – можно узнать много нового о том, что можно узнать с помощью WMI.

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

  static IEnumWbemClassObject* pEnumerator ;
  static IWbemLocator *pLoc ;
  static IWbemServices *pSvc;
  static IWbemClassObject *pclsObj;

DWORD WINAPI CCommandWindow::_ThreadProc(__in LPVOID lpParameter)
{
  CCommandWindow *pCommandWindow = static_cast<CCommandWindow *>(lpParameter);
  if (pCommandWindow == NULL)
  {
    return 0;
  }

  HRESULT hres;
  hres = CoInitializeEx(0, COINIT_MULTITHREADED);
  if (FAILED(hres))
    return 1;         // Program has failed.

  hres = CoCreateInstance(
    CLSID_WbemLocator,      
    0,
    CLSCTX_INPROC_SERVER,
    IID_IWbemLocator, (LPVOID *) &(pLoc));
  if (FAILED(hres))
  {
    CoUninitialize();
    return 1;         // Program has failed.
  }
  hres = pLoc->ConnectServer(
     _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
     NULL,          // User name. NULL = current user
     NULL,          // User password. NULL = current
     0,            // Locale. NULL indicates current
     NULL,          // Security flags.
     0,            // Authority (e.g. Kerberos)
     0,            // Context object
     &pSvc          // pointer to IWbemServices proxy
     );

  if (FAILED(hres))
  {
    pLoc->Release();  
    CoUninitialize();
    return 1;        // Program has failed.
  }

  hres = CoSetProxyBlanket(
    pSvc,            // Indicates the proxy to set
    RPC_C_AUTHN_WINNT,      // RPC_C_AUTHN_xxx
    RPC_C_AUTHZ_NONE,      // RPC_C_AUTHZ_xxx
    NULL,            // Server principal name
    RPC_C_AUTHN_LEVEL_CALL,   // RPC_C_AUTHN_LEVEL_xxx
    RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
    NULL,            // client identity
    EOAC_NONE          // proxy capabilities
  );

  if (FAILED(hres))
  {
    pSvc->Release();
    pLoc->Release();  
    CoUninitialize();
    return 1;        // Program has failed.
  }

  

  HRESULT hr = S_OK;

  // Create the window.
  pCommandWindow->_hInst = GetModuleHandle(NULL);
  if (pCommandWindow->_hInst != NULL)
  {      
    hr = pCommandWindow->_MyRegisterClass();
    if (SUCCEEDED(hr))
    {
      hr = pCommandWindow->_InitInstance();
    }
  }
  else
  {
    hr = HRESULT_FROM_WIN32(GetLastError());
  }
  ShowWindow(pCommandWindow->_hWnd, SW_HIDE);

  if (SUCCEEDED(hr))
  {    
    while (pCommandWindow->_ProcessNextMessage())
    {
    }
  }
  else
  {
    if (pCommandWindow->_hWnd != NULL)
    {
      pCommandWindow->_hWnd = NULL;
    }
  }

  return 0;
}

* This source code was highlighted with Source Code Highlighter.


соответственно в деструкторе CommandWindow освобождаем ресурсы:

  pSvc->Release();
  pLoc->Release();
  pEnumerator->Release();
  pclsObj->Release();

* This source code was highlighted with Source Code Highlighter.


И теперь самое интересное – обработчик события WM_DEVICECHANGE:

  case WM_DEVICECHANGE:
    {
      HRESULT hres = pSvc->ExecQuery(
        bstr_t("WQL"),
        bstr_t("SELECT * FROM Win32_DiskDrive"),
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
        NULL,
        &pEnumerator);
      if (FAILED(hres))
      {
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return 1;        // Program has failed
      }

      ULONG uReturn = 0;
     
      while (pEnumerator)
      {
        HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
          &pclsObj, &uReturn);

        if(0 == uReturn)
        {
          break;
        }

        VARIANT vtProp;

        hr = pclsObj->Get(L"PNPDeviceID", 0, &vtProp, 0, 0);
        if (wcscmp(vtProp.bstrVal,PNPID) == 0)
        {
          PostMessage(hWnd, WM_TOGGLE_CONNECTED_STATUS, 0, 0);
        }
        VariantClear(&vtProp);

        pclsObj->Release();
      }
    }
    break;

* This source code was highlighted with Source Code Highlighter.


Здесь происходит выполнение WQL-запроса к Win32_DiskDrive – списку наших дисков. Т.е. по PNPID мы можем привязаться к флешке, внешнему харду, но не к примеру usb-мышке(хотя это тоже можно реализовать!).

После выполенения запроса я сравниваю полученную строку(vtProp.bstrVal) с захардкоженным PNPID в consts.h:

static const wchar_t* PNPID = (L"USBSTOR\\DISK&VEN_CBM&PROD_FLASH_DISK&REV_5.00\\192023004CB4C702&0");

* This source code was highlighted with Source Code Highlighter.


PNPID можно подсмотреть в том же WMI Browser или можно воспользоваться средствами Visual Studio для работы с WMI.

Если они совпали – то я отправляю сообщение для включения флага подключения нужной флешки, по которому устанавливается этот флаг и вызывается метод обновления у провайдера.

Далее остался финальный штрих – заменить GUID нашей библиотеки с дефолтного на случайный в register.reg/unregister.reg и в guid.h.

Вот и всё. Далее дело за малым – скомпилировать проект, скопировать полученную библиотеку в System32, и выполнить register.reg. Потом нажимаем Win+L(блокировка системы) и наслаждаемся :)

Вот тут можно скачать исходники.

Что стоит доработать?



Убрать хардкод PNPID и логин/пароль. Лучше в отдельном софте сериализовать токен авторизации, и уже его использовать в дллке. Можно воспользоваться криптографическими средствами и хранить логин/пароль на флешке зашифрованными в определенном файле.

Что я хочу показать этой статьей – в Windows очень многое можно расширить под себя, главное – не бояться искать то, что хотите узнать, и пользоваться Windows SDK.

Всем удачи, надеюсь кого-то вдохновил на разработку чего-то полезного!
_________
Текст подготовлен в ХабраРедакторе

комментарии (60)

  • Давно пора было заменить, писать свою GINA.dll не очень приятно.
    • Мне к счастью не приходилось, поэтому не знаю как было раньше. Может расскажете в двух словах?
      • Вкратце примерно так: при входе WinLogon загружает msgina.dll, которая экспортирует штук 20 функций. Писать с нуля — задача на любителя. Обычно пишут обертку на стандартной. Как правило «заворачивают» все функции.
        Если очень интересно, то можно почитать об этом тут: www.rsdn.ru/article/baseserv/extgina.xml
        • Спасибо за полезную информацию! :)
        • О да, я помню это извращение! ))
          Дебагинг всего этого был просто неописуемым ))

          Помню, чтобы была возможность загрузиться, когда что-то пошло не так, я засунул свою gina.dll на флешку, и если что — выдирал ее из компа )))
          • удаленную отладку в совокупности с виртуальной машиной надо использовать под такие вещи имхо.
            • Не уверен, что это можно отлаживать удаленной отладкой… В любом случае, я тогда совсем зеленый был, и про такие вещи как удаленная отладка и виртуальные машины еще не слышал :)
              • можно, почему нельзя… под ntsd можно загнать winlogon изменив gflags-и, а можно и msvsmon сервисом поставить и работать через студии как обычно… по крайней мере через ntsd я отлаживал винлогон, но это было еще в win2k, может что и поменялось…
        • Есть замечательный OpenSource проект pGina — там достаточно написать плагин, обязанность которого авторизовать.
          (Я писал плагин, чтобы ребёнку ограничить время сидения за компьютером: другие программы, такие как TimeBoss, он как-то обходит)
          • Рано или поздно — и это обойдет :)
  • 40 тысяч обезьян улыбнули

    А если флешка потеряется?
    • значит удаляем дллку, чтобы тот кто её нашел не сильно радовался, и входим по паролю :)
      вход по паролю не отключаем — более того, credential providers и фильтры в безопасном режиме не работают :)
      • поправлюсь, они не работают по умолчанию, но через групповую политику их можно заставить работать… но я бы не рискнул :)
  • By флешка будет не единственный способ аутентификации?

    Т.е. сможем ли мы потом войти by руки?

    // Весь код и логику не читал
    • мы сделал credential provider. он нам дал дополнительный вариант входа — по флешке. старые мы не трогали => они остались, т.е. так же пароль, отпечаток пальца, что было — то осталось.

      чтобы убрать лишнее — надо либо написать фильтр либо убрать запись в реестре о других провайдерах входа.
  • Пароль классный. Остальное не читал.
  • Я знаю человека с таким-же паролем. Пора заносить в словари.
    • пора читать литературу ;)
      С. Лукьяненко — Лабиринт Оражений :)
      • Ах вот оно что :)
      • так и хочется спросить
        «Один на всех?!» (с)
      • Пора пить ноотропил;)
        С. Лукьяненко — «Фальшивые зеркала»

        ;)
        • я имел ввиду трилогию а не первую книгу ;)

          пора пить гиннес из канистры ;)
  • Вообще, глупо хранить в флешке или в коде провайдера пароль. Надо либо его там не хранить, либо если этого требует модель безопасности, хранить уникальный (для провайдера) код.
    • само собой. с другой стороны — что мешает в провайдере хранить шифрованный ключ, а на флешке — ключ шифрования?
      я хотел простое решение для домашнего компьютера, а не безопасное для энтерпрайза.
  • Вероятно :), если прикладывать мозг и руки, то винду можно будет настроить так же тонко, как и никсы разные.
    Только вот сложнее, да.
  • На учебе по линуксу была именно такая задача: вход по флешке. Ничего не кодили. Савили пакеты, генерили ключ и все. Не для холивара…
    • Теперь если на учёбе по винде будет именно такая задача, поставьте пакет автора топика. Ничего не надо будет кодить.
    • Ну если можно поставить пакет, то можно вообще задействовать TPM с аппаратным идентификатором вроде iButton + биометрия с полностью шифрованными разделами. Это не проблема. Было бы желание, ну и под виндовс такие пакеты как правило платные + цена самих tpm.
  • про обезьянм — это из лабиринта отражений Лукъяненко.

    а если потеряется флешка — винду придется сносить. это как со смарт-картами, если настроить аутентификацию на одного юзера.
    • Ничего сносить не придётся, это один из способов логина. Потерялась флешка — удалите dll, зайдя из Safe Mode, и логиньтесь дальше со своим обычным паролем.
      • а можно будет такую флешку восстановить при потере? ну данные на ней (ключ) засэйвинь в интернете где-нибудбь например
    • скажу по секрету что очень редко действительно надо сносить винду. в данном случае есть масса способов решить проблему не снося винду, от не-выключения стандартного режима входа по паролю(да-да, под одним и тем же пользователем можно войти разными способами!), до исправления реестра из диска восстановления винды :)
  • Сделайте конфигуратор
    • а ещё переписать всё без заплаток, сделать конфигуратор, удобный установщик, гарантировать безопасность и надежность и отсутствие утечек памяти — так можно и свой коммерческий продукт сделать.

      проблема в том что это всё — уже дело не двух вечеров, а больше :(
      • Мне кажется я написал только про конфигуратор? Я ошибаюсь?
  • Ну что, кто реализует целую утилиту?))
  • интереснее сделать авторизацию не по флешке, а по «Bluetooth»

    например подходим к компьютеру, активируем — нас впускает
    отходим, сигнал пропадает или ослабевает — блокировка
    • технически — вход можно реалиховать так же. только сканировать все PNP-устройства на предмет виртуального ком-порта или чего-либо ещё(смотря какой bluetooth-профиль используем), а на устройстве+компьютере настроить автоматический pairing в случае нахождения девайса в видимом радиусе :)

      впринципе вышеуказанное + сервис/приложение работающее в фоне которое по пропадании коннекта — блокирует винду = то что вы хотите :)

      а вообще такие решения уже есть, насколько я знаю…
      • по ID устройства, теоретически речь идет только о телефонах/кпк, хотя глобально — без разницы

        да, подобное решение реализовано, но не сработало, сделано уж больно криво
    • села батарея — приехали :)
      • у меня запасная всегда есть )

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

        а таймаут пароля через 1/5/10/30… минут «не активности» не есть хорошо
    • Какая-то софтинка для сони-ериксонов могла блокировать компьютер по исчезновению телефона из пределов видимости. Логин реализован не был. С другой стороны, дело было года три назад, может уже и изменилось что… Вон, в описании Rohos Logon про блютус что-то написано.

      Лично мне интереснее вариант использования чего-нибудь типа проксимити-карты… Но для нее специально обученный ридер надо покупать.
      • про нее и говорил, единственноч что нашел с Bluetooth, но не сумел корректно завести ее.

        если аналогов не найдется, покопаю еще раз ее
    • Угощайтесь www.rohos.ru/welcome/bluetooth_login.htm
  • Если кому хочется готового, то можно глянуть на www.rohos.ru/products/rohos-logon-key/ или www.dekart.com/products/access_control/logon/

    За деньги, но демка есть и там, и там. Декартом я несколько лет назад пользовался под ХР, вроде особых проблем не было. Потом надоело. :)
  • Интересно, но пара небольших замечаний.
    Во-первых, все устройства (в частности и диски и мыши) являются наследниками CIM_LogicalDevice.
    Во-вторых, вместо итерирования всех устройств с последующим сравнением, лучше сразу создавать WQL запрос с ключевым словом WHERE:
    SELECT * FROM CIM_LogicalDevice WHERE PNPDeviceId = 'USBSTOR\DISK&VEN_CBM&PROD_FLASH_DISK&REV_5.00\192023004CB4C702&0'
  • автор упрям и усидчив.
    • вы не представляете как вы ошибаетесь :)
      автору просто было очень скучно :(
  • Credential Provider — это объект COM?
    • да
      • ну значит в теории то же самое можно сделать и на .NET.
        не хотите написать вторую часть статьи? :)
        • сделать то можно, но лично мне не кажется правильным писать такие вещи на управляемом коде… если при логоне начнет подгружаться CLR ради такой маленькой вещи то это не очень хорошо ИМХО.

          как бы я ни любил .NET :)
          • а в чем проблема?
            • проблема в том что он грузится все-таки не мгновенно.
              хотя попробовать написать можно, я попробую и напишу о результатах…
              • вот этот вопрос и интересно исследовать.
                мне кажется нормально там все со скоростью загрузки должно быть.
  • Ура! У меня заработало! Спасибо автору статьи!!!
  • pam_usb for Windows…
Только авторизованные пользователи могут оставлять комментарии. Авторизуйтесь, пожалуйста.