Windows屏幕解锁服务原理及实现(1)
https://github.com/zk2013/windows_remote_lock_unlock_screen
将生成的DLL注册至注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers ,之后的每次锁屏或开机登录都会加载这个DLL。
实现这个DLL有相关的微软文档:
ICredentialProviderCredential 接口
ICredentialProviderCredential2 接口
以上操作只实现了替换解锁Windows的登录界面功能,接下来是实现自动登录(自动解锁)。
实现自动解锁需要有通信机制发送接收Windows账号密码,接收端代码里使用的是 C++的 CreateNamedPipe (命名管道)。
在 ICredentialProviderCredential 接口的现实方法 GetSerialization 内创建了一个 pipe,管道名是 \\.\pipe\NoninteractiveUnlockCredentialProvider,( \\.\管道\管道名。最多可达256个字符的长度,而且不用区分大小写 )。
CPipeListener *pPipeListener = static_cast<CPipeListener *>(lpParameter);
LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\NoninteractiveUnlockCredentialProvider");
hPipe = CreateNamedPipe(
lpszPipename,
PIPE_ACCESS_INBOUND,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFSIZE, BUFSIZE,
0,
&sa);
fConnected = ConnectNamedPipe(hPipe, NULL);
fSuccess = FALSE;
cbUsername = 0;
cbPassword = 0;
fSuccess = ReadFile(hPipe, pPipeListener->_pwzUsername, ALLOCSIZE, &cbUsername, NULL);
fSuccess &= ReadFile(hPipe, pPipeListener->_pwzPassword, ALLOCSIZE, &cbPassword, NULL);
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
if (fSuccess && cbUsername && cbUsername)
{
pPipeListener->_fUnlocked = TRUE;
pPipeListener->_pProvider->OnUnlockingStatusChanged();
break;
}
现在实现发送端,使用 C++ 的 CreateFile (这是一个多功能的函数,可打开或创建文件或者I/O设备,并返回可访问的句柄:控制台,通信资源,目录(只读打开),磁盘驱动器,文件,邮槽,管道。)发送账号密码至管道 ( \\.\pipe\NoninteractiveUnlockCredentialProvider ) 。
HANDLE hPipe;
BOOL fSuccess = FALSE;
DWORD cbToWrite, cbWritten;
// Wait pipe
if (!WaitNamedPipe(lpszPipename, 5000))
{
_tprintf(TEXT("Could not open pipe.\n"));
return;
}
// Open pipe
hPipe = CreateFile(
lpszPipename, // pipe name
GENERIC_WRITE, // write access
0, // no sharing
NULL, // default security attributes
OPEN_EXISTING, // opens existing pipe
0, // default attributes
NULL); // no template file
if (hPipe == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("Could not open pipe. GLE=%d\n"), GetLastError());
return;
}
// Send username
cbToWrite = (lstrlen(lpvUsername) + 1) * sizeof(TCHAR);
fSuccess = WriteFile(
hPipe, // pipe handle
lpvUsername, // message
cbToWrite, // message length
&cbWritten, // bytes written
NULL); // not overlapped
if (!fSuccess)
{
_tprintf(TEXT("WriteFile to pipe failed. GLE=%d\n"), GetLastError());
CloseHandle(hPipe);
return;
}
// Send password
cbToWrite = (lstrlen(lpvPassword) + 1) * sizeof(TCHAR);
fSuccess = WriteFile(
hPipe, // pipe handle
lpvPassword, // message
cbToWrite, // message length
&cbWritten, // bytes written
NULL); // not overlapped
if (!fSuccess)
{
_tprintf(TEXT("WriteFile to pipe failed. GLE=%d\n"), GetLastError());
CloseHandle(hPipe);
return;
}
CloseHandle(hPipe);
接收端收到账号密码后,在 GetSerialization 方法内 通过 KerbInteractiveUnlockLogonInit 和 KerbInteractiveUnlockLogonPack 校验登录。正确后触发Windows登录机制并解开屏幕锁。
HRESULT CSampleCredential::GetSerialization(
CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr,
CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs,
PWSTR* ppwszOptionalStatusText,
CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon
)
{
UNREFERENCED_PARAMETER(ppwszOptionalStatusText);
UNREFERENCED_PARAMETER(pcpsiOptionalStatusIcon);
KERB_INTERACTIVE_LOGON kil;
ZeroMemory(&kil, sizeof(kil));
HRESULT hr;
WCHAR wsz[MAX_COMPUTERNAME_LENGTH+1];
DWORD cch = ARRAYSIZE(wsz);
if (GetComputerNameW(wsz, &cch))
{
PWSTR pwzProtectedPassword;
hr = ProtectIfNecessaryAndCopyPassword(_rgFieldStrings[SFI_PASSWORD], _cpus, &pwzProtectedPassword);
if (SUCCEEDED(hr))
{
KERB_INTERACTIVE_UNLOCK_LOGON kiul;
// Initialize kiul with weak references to our credential.
hr = KerbInteractiveUnlockLogonInit(wsz, _pwzUsername, _pwzPassword, _cpus, &kiul);
if (SUCCEEDED(hr))
{
// We use KERB_INTERACTIVE_UNLOCK_LOGON in both unlock and logon scenarios. It contains a
// KERB_INTERACTIVE_LOGON to hold the creds plus a LUID that is filled in for us by Winlogon
// as necessary.
hr = KerbInteractiveUnlockLogonPack(kiul, &pcpcs->rgbSerialization, &pcpcs->cbSerialization);
if (SUCCEEDED(hr))
{
ULONG ulAuthPackage;
hr = RetrieveNegotiateAuthPackage(&ulAuthPackage);
if (SUCCEEDED(hr))
{
pcpcs->ulAuthenticationPackage = ulAuthPackage;
pcpcs->clsidCredentialProvider = CLSID_CSampleProvider;
// At this point the credential has created the serialized credential used for logon
// By setting this to CPGSR_RETURN_CREDENTIAL_FINISHED we are letting logonUI know
// that we have all the information we need and it should attempt to submit the
// serialized credential.
*pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
}
}
}
CoTaskMemFree(pwzProtectedPassword);
}
}
else
{
DWORD dwErr = GetLastError();
hr = HRESULT_FROM_WIN32(dwErr);
}
return hr;
}