One terrible Friday evening I wondered how the login to the system (Windows 7) was implemented, which is so often used on laptops. What most interested me was how such transparent integration with WinLogon (the login mechanism) was made.
With the help of a friend, I learned that this is called the Credential Provider (at least since Vista, before it - there was another mechanism). And here I remembered that I had long wanted to do so that the system would be unlocked by connecting one specific flash drive. Therefore, I wanted to quickly build such a project.
The first thing I did was look for examples of the implementation of the Credential Provider. They were quickly found in the
Windows SDK , as well as separate
examples (but outdated ones for whists).
Among the examples was the one closest to me - SampleHardwareEventCredentialProvider.
')
I'll tell you briefly how the mechanism of account providers (Credential Provider) is arranged.
There are two branches in the registry (HKLM \ Software \ Microsoft Windows \ CurrentVersion \ Authentification) - Credential Providers themselves and Credential Provider Filters. In each of them, a set of the “branch-GUID” type (GUID is a unique identifier) ​​with the default parameter — the name of the provider or filter. Here it is necessary to clarify what providers and filters are.
Provider - it gives us the ability to log into the system. For example, by fingerprint, by entering a password (default behavior), by smart card, etc.
Filters - filter “extra” behavior from the user. Example - if the security policy prohibits smart card login - the filter may disable such provider.
Further, in the registry in all known horrible place HKCR \ CLSID described GUIDs, and in our case - for the identifiers of filters and providers their names are written, the file name is dll, and the thread model (ThreadingModel = Apartment).
We will not consider filters, we will pass specifically to the provider.
In the project we have several classes:
CSampleProvider, which implements the ICredentialProvider interface. It is he who is responsible for the login mechanism and the provision of credentials (Credential)
CSampleCredential, implementing the ICredentialProviderCredential. Actually here are the username, password (or authorization token, which is better). We give WinLogon a copy of this class to enter the system.
CommandWindow, stub window class.
The default behavior in SampleHardwareEventCredentialProvider is:
When the main class is created by the WinLogon input mechanism, a window with a button is created in another thread that simulates a device connection event. When you click on the button - the transition to the login with the account “Administrator”.
What do I need to change the minimum of this behavior to be a working solution for me?
Hide the window, change the hardwired “Administrator” account to mine (with auto-enter password), enable autologin, and implement the WM_DEVICECHANGE event.
Hide the window was easy - just find ShowWindow (hWnd, SW_SHOW); and replace with SW_HIDE
Changing a hard-coded account was also easy - I created a separate file, consts.h, where I wrote:
static const wchar_t* USERNAME = (L " " );
static const wchar_t* PASSWORD = (L " ." );
* This source code was highlighted with Source Code Highlighter .
It was also easy to turn on autologin:
HRESULT CSampleCredential::SetSelected(__out BOOL* pbAutoLogon)
{
*pbAutoLogon = TRUE; // FALSE
return S_OK;
}
* This source code was highlighted with Source Code Highlighter .
This method is called when a user is selected to log in.
The only thing left is to implement the WM_DEVICECHANGE event.
In it, I need to find some unique identifier of the flash drive, compare it with the reference one, and in case of a match, set the flag for the device you need, which will then read CSampleCredential.
What could be unique to a flash drive? In general, a lot of things, VendorID / ProductID (unique to the product, i.e. the same series of flash drives - the same), the serial number of the partition (cleared when reformatting). I compare PNPID through the WMI (Windows Management Instrumentation) mechanism.
In general, you can talk about WMI for a long time, I will just say that this means of obtaining information and managing a bunch of OS components and not only, and has its own SQL-like query language called WQL. There is a wonderful utility from Microsoft called WMI Browser, I strongly advise you to install it - you can learn a lot about what you can learn with WMI.
Here is a modified flow procedure, in which, in addition to creating a window, now also initializes global static variables for working with 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 (eg 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 .
accordingly, in the CommandWindow destructor, we release the resources:
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
pclsObj->Release();
* This source code was highlighted with Source Code Highlighter .
And now the most interesting thing is the WM_DEVICECHANGE event handler:
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 .
This is where the WQL query to Win32_DiskDrive, the list of our disks, is executed. Those. by PNPID, we can attach to a flash drive, external hard drive, but not for example with a usb mouse (although this can also be implemented!).
After executing the query, I compare the resulting string (vtProp.bstrVal) with the hard-coded PNPID in 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 can be found in the same WMI Browser or you can use Visual Studio tools to work with WMI.
If they match, then I send a message to turn on the connection flag of the desired flash drive, by which this flag is set and the update method of the provider is called.
Next was the final touch - replace the GUID of our library from default to random in register.reg / unregister.reg and in guid.h.
That's all. Then it is easy to compile the project, copy the resulting library to System32, and execute register.reg. Then press Win + L (system lock) and enjoy :)
Here you can download the
source .
What should be finalized?
Remove the PNPID hardcode and login / password. It is better to serialize the authorization token in a separate software, and already use it in dll. You can use cryptographic tools and store the login / password on a flash drive encrypted in a specific file.
What I want to show with this article is that in Windows you can expand a lot for you, the main thing is not to be afraid to search for what you want to know and use the Windows SDK.
Good luck to everyone, I hope someone inspired to develop something useful!