Pull to refresh

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

Reading time 9 min
Views 39K
Одним ужасным пятничным вечером мне стало интересно как реализован вход в систему(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.

Всем удачи, надеюсь кого-то вдохновил на разработку чего-то полезного!
Tags:
Hubs:
+123
Comments 63
Comments Comments 63

Articles