
Logging in to connect a specific flash drive
One terrible Friday night, I wondered how the fingerprint login system (Windows 7), which is so often used on laptops, is implemented. What interested me the most was how such transparent integration with WinLogon (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 was a different mechanism). And then I remembered what I had long wanted to do so that the system unlocked when you connect one specific flash drive. Therefore, I wanted to quickly create such a project.
The first thing I did was look for implementation examples 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 briefly describe how the Credential Provider mechanism works.
There are two branches in the registry (HKLM \ Software \ Microsoft Windows \ CurrentVersion \ Authentification) - actually Credential Providers and Credential Provider Filters. In each of them, a set of the form “branch-GUID” (GUID - a unique identifier) with a default parameter - the name of the provider or filter. Here it is necessary to explain what providers and filters are.
Provider - it gives us the ability to log into the system. For example, by fingerprint, password entry (default behavior), by smart card, etc.
Filters - filter the "extra" behavior from the user. Example - if the security policy prohibits smart card login, a filter can disable such a provider.
Further, GUIDs are described in the registry in the well-known scary place HKCR \ CLSID, and in our case, their identifiers are specified for filter identifiers and providers, the file name is dlls, 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 credential
CSampleCredential, which implements the unpronounceable ICredentialProviderCredential interface. Actually, the username, password (or authorization token, which is better) are indicated here. We give an instance of this class to WinLogon for login.
CommandWindow, the stub window class.
By default, the following behavior in SampleHardwareEventCredentialProvider:
When creating the main class with the WinLogon login mechanism, a window with a button is created in another thread that simulates a device connection event. When you click on the button, you are transferred to the login with the “Administrator” account.
What do I need to change minimally from this behavior so that there is a working solution for me?
Hide the window, change the hard-coded “Administrator” account to mine (with auto-password), enable auto-login, and implement the WM_DEVICECHANGE event.
Hiding 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 consts.h file, where I registered:
Turning on auto-login was also easy:
This method is called when a user is selected to log in.
The only difficult thing left is to implement the WM_DEVICECHANGE event.
In it I need to find some unique identifier for the flash drive, compare it with the reference one, and in case of coincidence, set the flag that the necessary device is connected, which will then read CSampleCredential.
What can be unique with a flash drive? In general, there are a lot of things, VendorID / ProductID (unique to the product, i.e. the same series of flash drives coincides), the serial number of the partition (is reset when reformatting). I am comparing PNPIDs through the WMI (Windows Management Instrumentation) mechanism.
In general, you can talk about WMI for a long time, I’ll just say that this is a means of obtaining information and managing a bunch of OS components and not only, and it 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 using WMI.
Here is a modified stream procedure in which, in addition to creating a window, it also initializes global static variables for working with WMI:
accordingly, we free resources in the CommandWindow destructor:
And now the most interesting thing is the WM_DEVICECHANGE event handler:
This is where the WQL query to Win32_DiskDrive, the list of our drives, is executed. Those. by PNPID we can attach to a USB flash drive, an external hard drive, but not for example a usb mouse (although this can also be implemented!).
After fulfilling the request, I compare the resulting string (vtProp.bstrVal) with the hard-coded PNPID in consts.h:
PNPID can be spied in the same WMI Browser, or you can use Visual Studio tools for working with WMI.
If they match, then I send a message to enable the connection flag of the desired flash drive, by which this flag is set and the update method of the provider is called.
Then the final touch remained - to replace the GUID of our library from default to random in register.reg / unregister.reg and guid.h.
That's all. Next, it’s small: compile the project, copy the resulting library to System32, and execute register.reg. Then we press Win + L (system lock) and enjoy :)
Here you can download the source .
Remove the PNPID hardcode and username / password. It is better to serialize the authorization token in a separate software, and already use it in the dll. You can use cryptographic tools and store the username / password on a USB flash drive encrypted in a specific file.
What I want to show with this article is that you can expand a lot in Windows for yourself, the main thing is not to be afraid to look for what you want to know and use the Windows SDK.
Good luck to everyone, I hope someone inspired to develop something useful!
With the help of a friend, I learned that this is called the Credential Provider (at least since Vista, before it was a different mechanism). And then I remembered what I had long wanted to do so that the system unlocked when you connect one specific flash drive. Therefore, I wanted to quickly create such a project.
The first thing I did was look for implementation examples 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 briefly describe how the Credential Provider mechanism works.
There are two branches in the registry (HKLM \ Software \ Microsoft Windows \ CurrentVersion \ Authentification) - actually Credential Providers and Credential Provider Filters. In each of them, a set of the form “branch-GUID” (GUID - a unique identifier) with a default parameter - the name of the provider or filter. Here it is necessary to explain what providers and filters are.
Provider - it gives us the ability to log into the system. For example, by fingerprint, password entry (default behavior), by smart card, etc.
Filters - filter the "extra" behavior from the user. Example - if the security policy prohibits smart card login, a filter can disable such a provider.
Further, GUIDs are described in the registry in the well-known scary place HKCR \ CLSID, and in our case, their identifiers are specified for filter identifiers and providers, the file name is dlls, 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 credential
CSampleCredential, which implements the unpronounceable ICredentialProviderCredential interface. Actually, the username, password (or authorization token, which is better) are indicated here. We give an instance of this class to WinLogon for login.
CommandWindow, the stub window class.
By default, the following behavior in SampleHardwareEventCredentialProvider:
When creating the main class with the WinLogon login mechanism, a window with a button is created in another thread that simulates a device connection event. When you click on the button, you are transferred to the login with the “Administrator” account.
What do I need to change minimally from this behavior so that there is a working solution for me?
Hide the window, change the hard-coded “Administrator” account to mine (with auto-password), enable auto-login, and implement the WM_DEVICECHANGE event.
Hiding 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 consts.h file, where I registered:
static const wchar_t* USERNAME = (L"Кирилл Орлов");
static const wchar_t* PASSWORD = (L"Сорок тысяч обезьян в жопу сунули банан.");
* This source code was highlighted with Source Code Highlighter.
Turning on auto-login was also easy:
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 difficult thing left is to implement the WM_DEVICECHANGE event.
In it I need to find some unique identifier for the flash drive, compare it with the reference one, and in case of coincidence, set the flag that the necessary device is connected, which will then read CSampleCredential.
What can be unique with a flash drive? In general, there are a lot of things, VendorID / ProductID (unique to the product, i.e. the same series of flash drives coincides), the serial number of the partition (is reset when reformatting). I am comparing PNPIDs through the WMI (Windows Management Instrumentation) mechanism.
In general, you can talk about WMI for a long time, I’ll just say that this is a means of obtaining information and managing a bunch of OS components and not only, and it 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 using WMI.
Here is a modified stream procedure in which, in addition to creating a window, it 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(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.
accordingly, we free resources in the CommandWindow destructor:
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 drives, is executed. Those. by PNPID we can attach to a USB flash drive, an external hard drive, but not for example a usb mouse (although this can also be implemented!).
After fulfilling the request, 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 spied in the same WMI Browser, or you can use Visual Studio tools for working with WMI.
If they match, then I send a message to enable the connection flag of the desired flash drive, by which this flag is set and the update method of the provider is called.
Then the final touch remained - to replace the GUID of our library from default to random in register.reg / unregister.reg and guid.h.
That's all. Next, it’s small: compile the project, copy the resulting library to System32, and execute register.reg. Then we press Win + L (system lock) and enjoy :)
Here you can download the source .
What is worth finalizing?
Remove the PNPID hardcode and username / password. It is better to serialize the authorization token in a separate software, and already use it in the dll. You can use cryptographic tools and store the username / password on a USB flash drive encrypted in a specific file.
What I want to show with this article is that you can expand a lot in Windows for yourself, the main thing is not to be afraid to look for what you want to know and use the Windows SDK.
Good luck to everyone, I hope someone inspired to develop something useful!