Windows螢幕解鎖服務原理及實現(1)

路小路的日常發表於2023-05-06

https://github.com/zk2013/windows_remote_lock_unlock_screen

 

將生成的DLL註冊至登錄檔 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers ,之後的每次鎖屏或開機登入都會載入這個DLL。

實現這個DLL有相關的微軟文件:

ICredentialProvider 介面

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;
}

相關文章