diff --git a/ee/wcp/CMakeLists.txt b/ee/wcp/CMakeLists.txt index 232cfb49..922c9018 100644 --- a/ee/wcp/CMakeLists.txt +++ b/ee/wcp/CMakeLists.txt @@ -316,6 +316,8 @@ set(PROJECT_INSALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/ak_cred_provider/Release") add_subdirectory(cefexe) set_property(TARGET ak_cef PROPERTY MSVC_RUNTIME_LIBRARY MultiThreaded) +add_subdirectory(ak_lsa) + # Display configuration settings. PRINT_CEF_CONFIG() diff --git a/ee/wcp/Makefile b/ee/wcp/Makefile index ed90fedf..a8515343 100644 --- a/ee/wcp/Makefile +++ b/ee/wcp/Makefile @@ -4,7 +4,7 @@ include ../../common.mk OUT_TARGET := wcp -TARGETS := ak_cred_provider ak_common cefexe cefsimple +TARGETS := ak_cred_provider ak_lsa ak_common cefexe cefsimple CLANG_FORMAT := "C:\Program Files\LLVM\bin\clang-format.exe" FORMAT_FIND_ARGS := -iname '*.h' -o -iname '*.cpp' -o -iname '*.hpp' diff --git a/ee/wcp/ak_cred_provider/Credential.cpp b/ee/wcp/ak_cred_provider/Credential.cpp index b64ea227..0c42dc2e 100644 --- a/ee/wcp/ak_cred_provider/Credential.cpp +++ b/ee/wcp/ak_cred_provider/Credential.cpp @@ -307,13 +307,13 @@ LRESULT APIENTRY Credential::WndProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM .c_str()); if ((m_oCefAppData.pCefApp)) { spdlog::debug("WndProc:: CEFLaunch"); - pData->strUsername = ""; + pData->strUserToken = ""; try { CEFLaunch(pData, m_oCefAppData.pCefApp); } catch (const std::exception& e) { SPDLOG_WARN("Failed to CEFLaunch", e.what()); } - spdlog::debug(std::string("User logged in: " + pData->strUsername).c_str()); + spdlog::debug(std::string("User logged in: " + pData->strUserToken).c_str()); spdlog::debug("WndProc:: CEFLaunched"); } else { ::MessageBox(hWnd, @@ -751,25 +751,7 @@ IFACEMETHODIMP Credential::Connect(IQueryContinueWithStatus* pqcws) { } else { strCredUser = std::wstring(m_pszQualifiedUserName); } - std::wstring strAuthUser = - std::wstring(m_oHookData.strUsername.begin(), m_oHookData.strUsername.end()); - if ((strAuthUser == strCredUser) && (strCredUser != L"")) { - // Reset password - USER_INFO_1003 oUserInfo1003; - DWORD dwParamErr = 0; - m_strPass = GetRandomWStr(WIN_PASS_LEN); - oUserInfo1003.usri1003_password = (LPWSTR)(m_strPass.c_str()); - if (NetUserSetInfo(NULL, strCredUser.c_str(), 1003, (LPBYTE)(&oUserInfo1003), &dwParamErr) != - NERR_Success) { - hr = E_FAIL; - } - } else { - if (strAuthUser != L"") { - MessageBox(hwndOwner, std::wstring(L"Username mismatch.").c_str(), - (LPCWSTR)L"Login Failure", MB_OK | MB_TASKMODAL); - } - hr = E_FAIL; - } + m_strPass = utf8_decode(m_oHookData.strUserToken); } else { hr = E_POINTER; } diff --git a/ee/wcp/ak_cred_provider/Helpers.cpp b/ee/wcp/ak_cred_provider/Helpers.cpp index 2affff1e..ce4e1afe 100644 --- a/ee/wcp/ak_cred_provider/Helpers.cpp +++ b/ee/wcp/ak_cred_provider/Helpers.cpp @@ -303,7 +303,7 @@ HRESULT RetrieveNegotiateAuthPackage(_Out_ ULONG* pulAuthPackage) { if (SUCCEEDED(HRESULT_FROM_NT(status))) { ULONG ulAuthPackage; LSA_STRING lsaszKerberosName; - _LsaInitString(&lsaszKerberosName, NEGOSSP_NAME_A); + _LsaInitString(&lsaszKerberosName, "ak_lsa"); status = LsaLookupAuthenticationPackage(hLsa, &lsaszKerberosName, &ulAuthPackage); if (SUCCEEDED(HRESULT_FROM_NT(status))) { diff --git a/ee/wcp/ak_cred_provider/include/Credential.h b/ee/wcp/ak_cred_provider/include/Credential.h index 6f03268e..dbd70b1f 100644 --- a/ee/wcp/ak_cred_provider/include/Credential.h +++ b/ee/wcp/ak_cred_provider/include/Credential.h @@ -22,9 +22,9 @@ struct sHookData { hInstance = phInstance; oMutex.unlock(); } - void UpdateUser(const std::string& strUser) { + void UpdateUserToken(const std::string& strUser) { oMutex.lock(); - strUsername = strUser; + strUserToken = strUser; oMutex.unlock(); } void UpdateHeaderToken(const std::string& headerToken) { @@ -97,7 +97,7 @@ struct sHookData { } PWSTR UserSid = NULL; HINSTANCE hInstance = NULL; - std::string strUsername = ""; + std::string strUserToken = ""; std::string strHeaderToken = ""; bool bExit = false; // flag to exit the custom loop bool bComplete = false; // UI call complete diff --git a/ee/wcp/ak_lsa/CMakeLists.txt b/ee/wcp/ak_lsa/CMakeLists.txt new file mode 100644 index 00000000..7a6a9dc6 --- /dev/null +++ b/ee/wcp/ak_lsa/CMakeLists.txt @@ -0,0 +1,37 @@ +project(ak_lsa) + +set(CMAKE_BUILD_TYPE Release) +set(CMAKE_CXX_STANDARD 20) + +set(SRCS + PrepareProfile.cpp + PrepareToken.cpp + Main.cpp +) + +find_library(CREDUI_LIB_PATH Credui.lib) +find_library(SECUR32_LIB_PATH Secur32.lib) +find_library(SHLWAPI_LIB_PATH Shlwapi.lib) + +add_library(${PROJECT_NAME} SHARED + ${SRCS} +) +target_compile_definitions(${PROJECT_NAME} PUBLIC UNICODE _UNICODE SECURITY_WIN32) + +include_directories( + include +) + +include_directories(${PROJECT_NAME} PUBLIC ..) + +target_link_libraries(${PROJECT_NAME} + ${CREDUI_LIB_PATH} + ${SECUR32_LIB_PATH} + ${SHLWAPI_LIB_PATH} + authentik_sys_bridge + ak_common + spdlog +) +set_property(TARGET ak_lsa PROPERTY MSVC_RUNTIME_LIBRARY MultiThreaded) +set_property(TARGET authentik_sys PROPERTY MSVC_RUNTIME_LIBRARY MultiThreaded) +set_property(TARGET authentik_sys_bridge PROPERTY MSVC_RUNTIME_LIBRARY MultiThreaded) diff --git a/ee/wcp/ak_lsa/Main.cpp b/ee/wcp/ak_lsa/Main.cpp new file mode 100644 index 00000000..90d60ba7 --- /dev/null +++ b/ee/wcp/ak_lsa/Main.cpp @@ -0,0 +1,292 @@ +#include "PrepareToken.hpp" +#include "PrepareProfile.hpp" +#include "Utils.hpp" +#include "ak_common/include/ak_log.h" +#include "ak_common/include/ak_sentry.h" +#include "spdlog/spdlog.h" + +// exported symbols +#pragma comment(linker, "/export:SpLsaModeInitialize") + +LSA_SECPKG_FUNCTION_TABLE FunctionTable; + +NTSTATUS NTAPI SpInitialize(_In_ ULONG_PTR PackageId, _In_ SECPKG_PARAMETERS* Parameters, + _In_ LSA_SECPKG_FUNCTION_TABLE* functionTable) { + ak_setup_logs("lsa"); + ak_setup_sentry("lsa"); + + spdlog::debug("SpInitialize"); + + spdlog::debug(" PackageId: %u", PackageId); + spdlog::debug(" Version: %u", Parameters->Version); + { + ULONG state = Parameters->MachineState; + spdlog::debug(" MachineState:"); + if (state & SECPKG_STATE_ENCRYPTION_PERMITTED) { + state &= ~SECPKG_STATE_ENCRYPTION_PERMITTED; + spdlog::debug(" - ENCRYPTION_PERMITTED"); + } + if (state & SECPKG_STATE_STRONG_ENCRYPTION_PERMITTED) { + state &= ~SECPKG_STATE_STRONG_ENCRYPTION_PERMITTED; + spdlog::debug(" - STRONG_ENCRYPTION_PERMITTED"); + } + if (state & SECPKG_STATE_DOMAIN_CONTROLLER) { + state &= ~SECPKG_STATE_DOMAIN_CONTROLLER; + spdlog::debug(" - DOMAIN_CONTROLLER"); + } + if (state & SECPKG_STATE_WORKSTATION) { + state &= ~SECPKG_STATE_WORKSTATION; + spdlog::debug(" - WORKSTATION"); + } + if (state & SECPKG_STATE_STANDALONE) { + state &= ~SECPKG_STATE_STANDALONE; + spdlog::debug(" - STANDALONE"); + } + if (state) { + // print resudual flags not already covered + spdlog::debug(" * Unknown flags: 0x%X", state); + } + } + spdlog::debug(" SetupMode: %u", Parameters->SetupMode); + // parameters not logged + Parameters->DomainSid; + Parameters->DomainName; + Parameters->DnsDomainName; + Parameters->DomainGuid; + + FunctionTable = *functionTable; // copy function pointer table + + spdlog::debug(" return STATUS_SUCCESS"); + return STATUS_SUCCESS; +} + +NTSTATUS NTAPI SpShutDown() { + ak_teardown_sentry(); + spdlog::debug("SpShutDown"); + spdlog::debug(" return STATUS_SUCCESS"); + ak_teardown_logs(); + return STATUS_SUCCESS; +} + +NTSTATUS NTAPI SpGetInfo(_Out_ SecPkgInfoW* PackageInfo) { + spdlog::debug("SpGetInfo"); + + // return security package metadata + PackageInfo->fCapabilities = SECPKG_FLAG_LOGON // supports LsaLogonUser + | SECPKG_FLAG_CLIENT_ONLY; // no server auth support + PackageInfo->wVersion = SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION; + PackageInfo->wRPCID = SECPKG_ID_NONE; // no DCE/RPC support + PackageInfo->cbMaxToken = 0; + PackageInfo->Name = (wchar_t*)L"ak_lsa"; + PackageInfo->Comment = (wchar_t*)L"authentik Token Authentication"; + + spdlog::debug(" return STATUS_SUCCESS"); + return STATUS_SUCCESS; +} + +/* Authenticate a user logon attempt. + Returns STATUS_SUCCESS if the login attempt succeeded. */ +NTSTATUS LsaApLogonUser(_In_ PLSA_CLIENT_REQUEST ClientRequest, _In_ SECURITY_LOGON_TYPE LogonType, + _In_reads_bytes_(SubmitBufferSize) VOID* ProtocolSubmitBuffer, + _In_ VOID* ClientBufferBase, _In_ ULONG SubmitBufferSize, + _Outptr_result_bytebuffer_(*ProfileBufferSize) VOID** ProfileBuffer, + _Out_ ULONG* ProfileBufferSize, _Out_ LUID* LogonId, + _Out_ NTSTATUS* SubStatus, + _Out_ LSA_TOKEN_INFORMATION_TYPE* TokenInformationType, + _Outptr_ VOID** TokenInformation, _Out_ LSA_UNICODE_STRING** AccountName, + _Out_ LSA_UNICODE_STRING** AuthenticatingAuthority) { + spdlog::debug("LsaApLogonUser"); + + { + // clear output arguments first in case of failure + *ProfileBuffer = nullptr; + *ProfileBufferSize = 0; + *LogonId = {}; + *SubStatus = 0; + *TokenInformationType = {}; + *TokenInformation = nullptr; + *AccountName = nullptr; + if (AuthenticatingAuthority) *AuthenticatingAuthority = nullptr; + } + + // input arguments + spdlog::debug(" LogonType: {}", (int)LogonType); // Interactive=2, RemoteInteractive=10 + ClientBufferBase; + spdlog::debug(" ProtocolSubmitBuffer size: {}", (int)SubmitBufferSize); + + // deliberately restrict supported logontypes + if ((LogonType != Interactive) && (LogonType != RemoteInteractive)) { + spdlog::debug(" return STATUS_NOT_IMPLEMENTED (unsupported LogonType)"); + return STATUS_NOT_IMPLEMENTED; + } + + // authentication credentials passed by client + auto* logonInfo = (MSV1_0_INTERACTIVE_LOGON*)ProtocolSubmitBuffer; + { + if (SubmitBufferSize < sizeof(MSV1_0_INTERACTIVE_LOGON)) { + spdlog::debug(" ERROR: SubmitBufferSize too small"); + return STATUS_INVALID_PARAMETER; + } + + // make relative pointers absolute to ease later access + logonInfo->LogonDomainName.Buffer = + (wchar_t*)((BYTE*)logonInfo + (size_t)logonInfo->LogonDomainName.Buffer); + logonInfo->UserName.Buffer = (wchar_t*)((BYTE*)logonInfo + (size_t)logonInfo->UserName.Buffer); + logonInfo->Password.Buffer = (wchar_t*)((BYTE*)logonInfo + (size_t)logonInfo->Password.Buffer); + } + + if (!ValidateToken(logonInfo)) { + spdlog::debug(" ValidateToken: failed"); + return STATUS_ACCOUNT_RESTRICTION; + } + // assign output arguments + + { + wchar_t computerName[MAX_COMPUTERNAME_LENGTH + 1] = {}; + DWORD computerNameSize = ARRAYSIZE(computerName); + if (!GetComputerNameW(computerName, &computerNameSize)) { + spdlog::debug(" return STATUS_INTERNAL_ERROR (GetComputerNameW failed)"); + return STATUS_INTERNAL_ERROR; + } + + // assign "ProfileBuffer" output argument + *ProfileBufferSize = GetProfileBufferSize(computerName, *logonInfo); + FunctionTable.AllocateClientBuffer(ClientRequest, *ProfileBufferSize, + ProfileBuffer); // will update *ProfileBuffer + + std::vector profileBuffer = + PrepareProfileBuffer(computerName, *logonInfo, (BYTE*)*ProfileBuffer); + FunctionTable.CopyToClientBuffer(ClientRequest, (ULONG)profileBuffer.size(), *ProfileBuffer, + profileBuffer.data()); // copy to caller process + } + + { + // assign "LogonId" output argument + if (!AllocateLocallyUniqueId(LogonId)) { + spdlog::debug(" ERROR: AllocateLocallyUniqueId failed"); + return STATUS_FAIL_FAST_EXCEPTION; + } + NTSTATUS status = FunctionTable.CreateLogonSession(LogonId); + if (status != STATUS_SUCCESS) { + spdlog::debug(" ERROR: CreateLogonSession failed with err: 0x%x", status); + return status; + } + + spdlog::debug(" LogonId: High=0x%x , Low=0x%x", LogonId->HighPart, LogonId->LowPart); + } + + *SubStatus = STATUS_SUCCESS; // reason for error + + { + // Assign "TokenInformation" output argument + LSA_TOKEN_INFORMATION_V2* tokenInfo = nullptr; + NTSTATUS subStatus = 0; + NTSTATUS status = UserNameToToken(&logonInfo->UserName, &tokenInfo, &subStatus); + if (status != STATUS_SUCCESS) { + spdlog::debug("ERROR: UserNameToToken failed with err: 0x%x", status); + *SubStatus = subStatus; + return status; + } + + *TokenInformationType = LsaTokenInformationV1; + *TokenInformation = tokenInfo; + } + + { + // assign "AccountName" output argument + std::wstring username = ToWstring(logonInfo->UserName).c_str(); + spdlog::debug(" AccountName: {}", utf8_encode(username)); + *AccountName = CreateLsaUnicodeString(logonInfo->UserName.Buffer, + logonInfo->UserName.Length); // mandatory + } + + if (AuthenticatingAuthority) { + // assign "AuthenticatingAuthority" output argument + *AuthenticatingAuthority = + (LSA_UNICODE_STRING*)FunctionTable.AllocateLsaHeap(sizeof(LSA_UNICODE_STRING)); + + if (logonInfo->LogonDomainName.Length > 0) { + std::wstring authority = ToWstring(logonInfo->LogonDomainName).c_str(); + spdlog::debug(" AuthenticatingAuthority: {}", utf8_encode(authority)); + *AuthenticatingAuthority = CreateLsaUnicodeString(logonInfo->LogonDomainName.Buffer, + logonInfo->LogonDomainName.Length); + } else { + spdlog::debug(" AuthenticatingAuthority: "); + **AuthenticatingAuthority = { + .Length = 0, + .MaximumLength = 0, + .Buffer = nullptr, + }; + } + } + + spdlog::debug(" return STATUS_SUCCESS"); + return STATUS_SUCCESS; +} + +void LsaApLogonTerminated(_In_ LUID* LogonId) { + spdlog::debug("LsaApLogonTerminated"); + spdlog::debug(" LogonId: High=0x%x , Low=0x%x", LogonId->HighPart, LogonId->LowPart); + spdlog::debug(" return"); +} + +SECPKG_FUNCTION_TABLE SecurityPackageFunctionTable = { + .InitializePackage = nullptr, + .LogonUser = LsaApLogonUser, + .CallPackage = nullptr, + .LogonTerminated = LsaApLogonTerminated, + .CallPackageUntrusted = nullptr, + .CallPackagePassthrough = nullptr, + .LogonUserEx = nullptr, + .LogonUserEx2 = nullptr, + .Initialize = SpInitialize, + .Shutdown = SpShutDown, + .GetInfo = SpGetInfo, + .AcceptCredentials = nullptr, + .AcquireCredentialsHandle = nullptr, + .QueryCredentialsAttributes = nullptr, + .FreeCredentialsHandle = nullptr, + .SaveCredentials = nullptr, + .GetCredentials = nullptr, + .DeleteCredentials = nullptr, + .InitLsaModeContext = nullptr, + .AcceptLsaModeContext = nullptr, + .DeleteContext = nullptr, + .ApplyControlToken = nullptr, + .GetUserInfo = nullptr, + .GetExtendedInformation = nullptr, + .QueryContextAttributes = nullptr, + .AddCredentialsW = nullptr, + .SetExtendedInformation = nullptr, + .SetContextAttributes = nullptr, + .SetCredentialsAttributes = nullptr, + .ChangeAccountPassword = nullptr, + .QueryMetaData = nullptr, + .ExchangeMetaData = nullptr, + .GetCredUIContext = nullptr, + .UpdateCredentials = nullptr, + .ValidateTargetInfo = nullptr, + .PostLogonUser = nullptr, + .GetRemoteCredGuardLogonBuffer = nullptr, + .GetRemoteCredGuardSupplementalCreds = nullptr, + .GetTbalSupplementalCreds = nullptr, + .LogonUserEx3 = nullptr, + .PreLogonUserSurrogate = nullptr, + .PostLogonUserSurrogate = nullptr, + .ExtractTargetInfo = nullptr, +}; + +/** LSA calls SpLsaModeInitialize() when loading SSP/AP DLLs. */ +extern "C" NTSTATUS NTAPI SpLsaModeInitialize(_In_ ULONG LsaVersion, _Out_ ULONG* PackageVersion, + _Out_ SECPKG_FUNCTION_TABLE** ppTables, + _Out_ ULONG* pcTables) { + spdlog::debug("SpLsaModeInitialize"); + spdlog::debug(" LsaVersion %u", LsaVersion); + + *PackageVersion = SECPKG_INTERFACE_VERSION; + *ppTables = &SecurityPackageFunctionTable; + *pcTables = 1; + + spdlog::debug(" return STATUS_SUCCESS"); + return STATUS_SUCCESS; +} diff --git a/ee/wcp/ak_lsa/PrepareProfile.cpp b/ee/wcp/ak_lsa/PrepareProfile.cpp new file mode 100644 index 00000000..68f4bf3f --- /dev/null +++ b/ee/wcp/ak_lsa/PrepareProfile.cpp @@ -0,0 +1,79 @@ +#include +#include +#include "PrepareProfile.hpp" +#include "Utils.hpp" + +static LARGE_INTEGER InfiniteFuture() { + LARGE_INTEGER val{ + .LowPart = 0xFFFFFFFF, // unsigned + .HighPart = 0x7FFFFFFF, // signed + }; + return val; +} + +static LARGE_INTEGER CurrentTime() { + FILETIME time{}; + GetSystemTimeAsFileTime(&time); + return LARGE_INTEGER{ + .LowPart = time.dwLowDateTime, + .HighPart = (LONG)time.dwHighDateTime, + }; +} + +ULONG GetProfileBufferSize(const std::wstring& computername, + const MSV1_0_INTERACTIVE_LOGON& logonInfo) { + return sizeof(MSV1_0_INTERACTIVE_PROFILE) + logonInfo.UserName.Length + + (ULONG)(2 * computername.size()); +} + +std::vector PrepareProfileBuffer(const std::wstring& computername, + const MSV1_0_INTERACTIVE_LOGON& logonInfo, + BYTE* hostProfileAddress) { + std::vector profileBuffer(GetProfileBufferSize(computername, logonInfo), (BYTE)0); + auto* profile = (MSV1_0_INTERACTIVE_PROFILE*)profileBuffer.data(); + size_t offset = sizeof(MSV1_0_INTERACTIVE_PROFILE); // offset to string parameters + + profile->MessageType = MsV1_0InteractiveProfile; + profile->LogonCount = 0; // unknown + profile->BadPasswordCount = 0; + profile->LogonTime = CurrentTime(); + profile->LogoffTime = InfiniteFuture(); // logoff reminder + profile->KickOffTime = InfiniteFuture(); // forced logoff + profile->PasswordLastSet.QuadPart = 0; // 1. January 1601 + profile->PasswordCanChange = InfiniteFuture(); // password change reminder + profile->PasswordMustChange = InfiniteFuture(); // password change required + profile->LogonScript; // observed to be empty + profile->HomeDirectory; // observed to be empty + { + // set "UserName" + memcpy(/*dst*/ profileBuffer.data() + offset, /*src*/ logonInfo.UserName.Buffer, + logonInfo.UserName.MaximumLength); + + LSA_UNICODE_STRING tmp = { + .Length = logonInfo.UserName.Length, + .MaximumLength = logonInfo.UserName.MaximumLength, + .Buffer = (wchar_t*)(hostProfileAddress + offset), + }; + profile->FullName = tmp; + + offset += profile->FullName.MaximumLength; + } + profile->ProfilePath; // observed to be empty + profile->HomeDirectoryDrive; // observed to be empty + { + // set "LogonServer" + memcpy(/*dst*/ profileBuffer.data() + offset, /*src*/ computername.data(), computername.size()); + + LSA_UNICODE_STRING tmp = { + .Length = (USHORT)(2 * computername.size()), + .MaximumLength = (USHORT)(2 * computername.size()), + .Buffer = (wchar_t*)(hostProfileAddress + offset), + }; + profile->LogonServer = tmp; + + offset += profile->LogonServer.MaximumLength; + } + profile->UserFlags = 0; + + return profileBuffer; +} diff --git a/ee/wcp/ak_lsa/PrepareToken.cpp b/ee/wcp/ak_lsa/PrepareToken.cpp new file mode 100644 index 00000000..013b0333 --- /dev/null +++ b/ee/wcp/ak_lsa/PrepareToken.cpp @@ -0,0 +1,148 @@ +#include "PrepareToken.hpp" +#include +#include "Utils.hpp" +#include "spdlog/spdlog.h" + +#pragma comment(lib, "Netapi32.lib") + +static bool NameToSid(const wchar_t* username, PSID* userSid) { + DWORD lengthSid = 0; + SID_NAME_USE Use = {}; + DWORD referencedDomainNameLen = 0; + BOOL res = LookupAccountNameW(nullptr, username, nullptr, &lengthSid, nullptr, + &referencedDomainNameLen, &Use); + + *userSid = (PSID)FunctionTable.AllocateLsaHeap(lengthSid); + wchar_t* referencedDomainName = (wchar_t*)FunctionTable.AllocateLsaHeap( + sizeof(wchar_t) * referencedDomainNameLen); // throwaway string + res = LookupAccountNameW(nullptr, username, *userSid, &lengthSid, referencedDomainName, + &referencedDomainNameLen, &Use); + if (!res) { + DWORD err = GetLastError(); + spdlog::debug(" LookupAccountNameW failed (err %u)", err); + return false; + } + + FunctionTable.FreeLsaHeap(referencedDomainName); + return true; +} + +static void GetPrimaryGroupSidFromUserSid(PSID userSID, PSID* primaryGroupSID) { + // duplicate the user sid + *primaryGroupSID = (PSID)FunctionTable.AllocateLsaHeap(GetLengthSid(userSID)); + CopySid(GetLengthSid(userSID), *primaryGroupSID, userSID); + + // replace the last subauthority by DOMAIN_GROUP_RID_USERS + // https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers + // (last SubAuthority = RID + // https://learn.microsoft.com/nb-no/windows/win32/secauthz/well-known-sids + UCHAR SubAuthorityCount = *GetSidSubAuthorityCount(*primaryGroupSID); + *GetSidSubAuthority(*primaryGroupSID, SubAuthorityCount - 1) = DOMAIN_GROUP_RID_USERS; +} + +static bool GetGroups(const wchar_t* UserName, GROUP_USERS_INFO_1** lpGroupInfo, + DWORD* pTotalEntries) { + DWORD NumberOfEntries = 0; + DWORD status = NetUserGetGroups(NULL, UserName, 1, (BYTE**)lpGroupInfo, MAX_PREFERRED_LENGTH, + &NumberOfEntries, pTotalEntries); + if (status != NERR_Success) { + spdlog::debug("ERROR: NetUserGetGroups failed with error %u", status); + return false; + } + return true; +} + +static bool GetLocalGroups(const wchar_t* UserName, GROUP_USERS_INFO_0** lpGroupInfo, + DWORD* pTotalEntries) { + DWORD NumberOfEntries = 0; + DWORD status = NetUserGetLocalGroups(NULL, UserName, 0, 0, (BYTE**)lpGroupInfo, + MAX_PREFERRED_LENGTH, &NumberOfEntries, pTotalEntries); + if (status != NERR_Success) { + spdlog::debug("ERROR: NetUserGetLocalGroups failed with error %u", status); + return false; + } + return true; +} + +NTSTATUS UserNameToToken(__in LSA_UNICODE_STRING* AccountName, + __out LSA_TOKEN_INFORMATION_V1** Token, __out PNTSTATUS SubStatus) { + const LARGE_INTEGER Forever{ + .LowPart = 0xFFFFFFFF, // unsigned + .HighPart = 0x7FFFFFFF, // signed + }; + + // convert username to zero-terminated string + std::wstring username = ToWstring(*AccountName); + + auto* token = + (LSA_TOKEN_INFORMATION_V1*)FunctionTable.AllocateLsaHeap(sizeof(LSA_TOKEN_INFORMATION_V1)); + + token->ExpirationTime = Forever; + + PSID userSid = nullptr; + const wchar_t* user = username.c_str(); + { + // configure "User" + if (!NameToSid(username.c_str(), &userSid)) return STATUS_FAIL_FAST_EXCEPTION; + spdlog::debug(" User.User: {}", utf8_encode(username)); + token->User.User = { + .Sid = userSid, + .Attributes = 0, + }; + } + + { + // configure "Groups" + DWORD NumberOfGroups = 0; + GROUP_USERS_INFO_1* pGroupInfo = nullptr; + if (!GetGroups(user, &pGroupInfo, &NumberOfGroups)) { + return STATUS_FAIL_FAST_EXCEPTION; + } + spdlog::debug(" NumberOfGroups: %u", NumberOfGroups); + + DWORD NumberOfLocalGroups = 0; + GROUP_USERS_INFO_0* pLocalGroupInfo = nullptr; + if (!GetLocalGroups(user, &pLocalGroupInfo, &NumberOfLocalGroups)) { + return STATUS_FAIL_FAST_EXCEPTION; + } + spdlog::debug(" NumberOfLocalGroups: %u", NumberOfLocalGroups); + + TOKEN_GROUPS* tokenGroups = (TOKEN_GROUPS*)FunctionTable.AllocateLsaHeap( + FIELD_OFFSET(TOKEN_GROUPS, Groups[NumberOfGroups + NumberOfLocalGroups])); + tokenGroups->GroupCount = NumberOfGroups + NumberOfLocalGroups; + for (size_t i = 0; i < NumberOfGroups; i++) { + NameToSid(pGroupInfo[i].grui1_name, &tokenGroups->Groups[i].Sid); + + tokenGroups->Groups[i].Attributes = pGroupInfo[i].grui1_attributes; + } + for (size_t i = 0; i < NumberOfLocalGroups; i++) { + NameToSid(pLocalGroupInfo[i].grui0_name, &tokenGroups->Groups[NumberOfGroups + i].Sid); + + // get the attributes of group since pLocalGroupInfo doesn't contain attributes + if (*GetSidSubAuthority(tokenGroups->Groups[NumberOfGroups + i].Sid, 0) != + SECURITY_BUILTIN_DOMAIN_RID) + tokenGroups->Groups[NumberOfGroups + i].Attributes = + SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT; + else + tokenGroups->Groups[NumberOfGroups + i].Attributes = 0; + } + + token->Groups = tokenGroups; + } + + GetPrimaryGroupSidFromUserSid(userSid, &token->PrimaryGroup.PrimaryGroup); + + // TOKEN_PRIVILEGES Privileges not currently configured + token->Privileges = nullptr; + + // PSID Owner not currently configured + token->Owner.Owner = (PSID) nullptr; + + // PACL DefaultDacl not currently configured + token->DefaultDacl.DefaultDacl = nullptr; + + // assign outputs + *Token = token; + *SubStatus = STATUS_SUCCESS; + return STATUS_SUCCESS; +} diff --git a/ee/wcp/ak_lsa/Utils.hpp b/ee/wcp/ak_lsa/Utils.hpp new file mode 100644 index 00000000..426719ec --- /dev/null +++ b/ee/wcp/ak_lsa/Utils.hpp @@ -0,0 +1,128 @@ +#pragma once +#include +#include +#include +#include "authentik_sys_bridge/ffi.h" +#include "rust/cxx.h" +#include "ak_common/include/ak_log.h" +#include "spdlog/spdlog.h" + +extern LSA_SECPKG_FUNCTION_TABLE FunctionTable; + +/** Allocate and create a new LSA_STRING object. + Assumes that "FunctionTable" is initialized. */ +inline LSA_STRING* CreateLsaString(const std::string& msg) { + auto msg_len = (USHORT)msg.size(); // exclude null-termination + + assert(FunctionTable.AllocateLsaHeap); + auto* obj = (LSA_STRING*)FunctionTable.AllocateLsaHeap(sizeof(LSA_STRING)); + obj->Buffer = (char*)FunctionTable.AllocateLsaHeap(msg_len); + memcpy(/*dst*/ obj->Buffer, /*src*/ msg.c_str(), msg_len); + obj->Length = msg_len; + obj->MaximumLength = msg_len; + return obj; +} + +/** Allocate and create a new LSA_UNICODE_STRING object. + Assumes that "FunctionTable" is initialized. */ +inline LSA_UNICODE_STRING* CreateLsaUnicodeString(const wchar_t* msg, USHORT msg_len_bytes) { + assert(FunctionTable.AllocateLsaHeap); + auto* obj = (LSA_UNICODE_STRING*)FunctionTable.AllocateLsaHeap(sizeof(LSA_UNICODE_STRING)); + obj->Buffer = (wchar_t*)FunctionTable.AllocateLsaHeap(msg_len_bytes); + memcpy(/*dst*/ obj->Buffer, /*src*/ msg, msg_len_bytes); + obj->Length = msg_len_bytes; + obj->MaximumLength = msg_len_bytes; + return obj; +} + +inline LSA_UNICODE_STRING* CreateLsaUnicodeString(const std::wstring& msg) { + return CreateLsaUnicodeString(msg.c_str(), (USHORT)msg.size() * sizeof(wchar_t)); +} + +inline std::wstring ToWstring(LSA_UNICODE_STRING& lsa_str) { + if (lsa_str.Length == 0) return L""; + return std::wstring(lsa_str.Buffer, lsa_str.Length / 2); +} + +inline void AssignLsaUnicodeString(const LSA_UNICODE_STRING& source, LSA_UNICODE_STRING& dest) { + assert(FunctionTable.AllocateLsaHeap); + if (dest.Buffer) FunctionTable.FreeLsaHeap(dest.Buffer); + + dest.Buffer = (wchar_t*)FunctionTable.AllocateLsaHeap(source.Length); + memcpy(/*dst*/ dest.Buffer, /*src*/ source.Buffer, source.Length); + dest.Length = source.Length; + dest.MaximumLength = source.Length; +} + +// Convert a wide Unicode string to an UTF8 string +inline std::string utf8_encode(const std::wstring& wstr) { + if (wstr.empty()) return std::string(); + int size_needed = + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} + +inline PWSTR decryptPassword(MSV1_0_INTERACTIVE_LOGON* pkil) { + CRED_PROTECTION_TYPE ProtectionType; + ULONG Length = pkil->Password.Length; + + spdlog::debug(" decryptPassword: Fixing pointers..."); + PWSTR pszCredentials = (PWSTR)FunctionTable.AllocateLsaHeap(Length + sizeof(WCHAR)); + memcpy(pszCredentials, pkil->Password.Buffer, pkil->Password.MaximumLength); + + spdlog::debug(" decryptPassword: Checking if password is encrypted..."); + if (!CredIsProtectedW(pszCredentials, &ProtectionType)) { + spdlog::debug(" decryptPassword: Password is not encrypted"); + return pszCredentials; + } + + ULONG cchToken = 0; + PWSTR pszToken = 0; + ULONG cchCredentials = Length / sizeof(WCHAR); + + HRESULT status; + + if (ProtectionType != CredUnprotected) { + spdlog::debug(" decryptPassword: Password is protected"); + while (true) { + spdlog::debug(" decryptPassword: CredUnprotectW call"); + if (CredUnprotectW(FALSE, pszCredentials, cchCredentials, pszToken, &cchToken)) { + break; + } + if (pszToken) { + break; + } + auto err = GetLastError(); + if (err == ERROR_INSUFFICIENT_BUFFER) { + spdlog::debug(" decryptPassword: ERROR_INSUFFICIENT_BUFFER, {}", cchToken); + pszToken = (PWSTR)FunctionTable.AllocatePrivateHeap(cchToken * sizeof(WCHAR)); + // pszToken = (PWSTR)alloca(cchToken * sizeof(WCHAR)); + } + } + } else { + spdlog::debug(" decryptPassword: PW was not encrypted"); + pszToken = pszCredentials; + cchToken = cchCredentials; + } + return pszToken; +} + +inline bool ValidateToken(MSV1_0_INTERACTIVE_LOGON* pkil) { + try { + spdlog::debug(" ak_sys_auth_token_validate: Decrypting password"); + std::wstring pwW = decryptPassword(pkil); + std::string pw = utf8_encode(pwW); + TokenResponse validatedToken; + spdlog::debug(" ak_sys_auth_token_validate: {:d}, {}", pw.length(), pw); + if (ak_sys_auth_token_validate(pw, validatedToken)) { + spdlog::debug(" ak_sys_auth_token_validate Succeeded"); + return true; + } + } catch (const rust::Error& ex) { + spdlog::debug(" ak_sys_auth_token_validate Error: {}", ex.what()); + return false; + } + return false; +} diff --git a/ee/wcp/ak_lsa/include/PrepareProfile.hpp b/ee/wcp/ak_lsa/include/PrepareProfile.hpp new file mode 100644 index 00000000..9ab73cca --- /dev/null +++ b/ee/wcp/ak_lsa/include/PrepareProfile.hpp @@ -0,0 +1,13 @@ + +#pragma once +#include +#include +#include // for MSV1_0_INTERACTIVE_LOGON +#include // for PLSA_CLIENT_REQUEST + +ULONG GetProfileBufferSize(const std::wstring& computername, + const MSV1_0_INTERACTIVE_LOGON& logonInfo); + +std::vector PrepareProfileBuffer(const std::wstring& computername, + const MSV1_0_INTERACTIVE_LOGON& logonInfo, + BYTE* hostProfileAddress); diff --git a/ee/wcp/ak_lsa/include/PrepareToken.hpp b/ee/wcp/ak_lsa/include/PrepareToken.hpp new file mode 100644 index 00000000..f8104aee --- /dev/null +++ b/ee/wcp/ak_lsa/include/PrepareToken.hpp @@ -0,0 +1,12 @@ +#pragma once +#pragma warning(disable : 4005) +#include +#pragma warning(default : 4005) +#include +#include +#include +#include // for LSA_STRING +#include // for LSA_DISPATCH_TABLE + +NTSTATUS UserNameToToken(__in LSA_UNICODE_STRING* AccountName, + __out LSA_TOKEN_INFORMATION_V1** Token, __out PNTSTATUS SubStatus); diff --git a/ee/wcp/cefsimple/cefsimple_win.cc b/ee/wcp/cefsimple/cefsimple_win.cc index 510ae412..855fcdf8 100644 --- a/ee/wcp/cefsimple/cefsimple_win.cc +++ b/ee/wcp/cefsimple/cefsimple_win.cc @@ -10,8 +10,8 @@ #include "cefsimple/simple_handler.h" #include "cefsimple/cefsimple_win.h" #include "ak_common/include/ak_log.h" -#include "ak_common/include/ak_sentry.h" -#include "ak_common/include/crypt.h" +#include +#include "crypt.h" #include "Credential.h" extern std::string g_strPath; @@ -87,7 +87,7 @@ int CEFLaunch(sHookData* pData, CefRefPtr pCefApp) { { SPDLOG_DEBUG("Sub-loop"); pHandler->CloseAllBrowsers(true); - pData->UpdateUser(""); + pData->UpdateUserToken(""); // pData->SetCancel(true); // // perform (at max) 10 precautionary loops even though 1 `CefDoMessageLoopWork()` // // seems to be sufficient @@ -117,7 +117,7 @@ int CEFLaunch(sHookData* pData, CefRefPtr pCefApp) { Sleep(5); // as precaution to relieve the CPU (though unlikely that its needed) } pHandler = nullptr; // Release for the destructor to be called subsequently - if (pData->strUsername == "") // User clicked the close button or cancel + if (pData->strUserToken == "") // User clicked the close button or cancel { spdlog::debug("Token empty"); pData->SetCancel(true); diff --git a/ee/wcp/cefsimple/simple_handler.h b/ee/wcp/cefsimple/simple_handler.h index bc0f0448..28d93a4e 100644 --- a/ee/wcp/cefsimple/simple_handler.h +++ b/ee/wcp/cefsimple/simple_handler.h @@ -96,15 +96,13 @@ class SimpleHandler : public CefClient, ", ThreadID: ", std::to_string(GetCurrentThreadId())); Hide(); m_pData->UpdateStatus(L"Authenticating, please wait..."); - TokenResponse validatedToken; + std::string validatedToken; try { - if (!ak_sys_auth_url(strURL, validatedToken)) { - SPDLOG_WARN("failed to validate token"); - } else { - SPDLOG_DEBUG("successfully validated token"); - m_pData->UpdateUser(validatedToken.username.c_str()); - } + ak_sys_auth_url(strURL, validatedToken); + spdlog::debug("successfully validated token"); + m_pData->UpdateUserToken(validatedToken); } catch (const rust::Error& ex) { + SPDLOG_WARN("failed to validate token"); SPDLOG_WARN("Exception in ak_sys_auth_url: ", ex.what()); } CloseAllBrowsers(false); diff --git a/src/ffi.rs b/src/ffi.rs index 8f4da69e..9d7e50bb 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -1,4 +1,4 @@ -use cxx::{CxxString, let_cxx_string}; +use cxx::CxxString; use std::collections::HashMap; use std::error::Error; use std::pin::Pin; @@ -29,7 +29,7 @@ mod ffi { fn ak_sys_ping(res: Pin<&mut CxxString>); fn ak_sys_auth_interactive_available() -> Result; - fn ak_sys_auth_url(url: &CxxString, token: &mut TokenResponse) -> Result; + fn ak_sys_auth_url(url: &CxxString, token: Pin<&mut CxxString>) -> Result<()>; fn ak_sys_auth_token_validate( raw_token: &CxxString, token: &mut TokenResponse, @@ -50,15 +50,15 @@ fn ak_sys_ping(res: Pin<&mut CxxString>) { fn ak_sys_auth_url( url: &CxxString, - token: &mut ffi::TokenResponse, -) -> Result> { + token: Pin<&mut CxxString>, +) -> Result<(), Box> { let p = Url::parse(url.to_str()?)?; let qm: HashMap<_, _> = p.query_pairs().into_owned().collect(); let raw_token = qm .get(TOKEN_QUERY_PARAM) .ok_or("failed to get token from URL")?; - let_cxx_string!(crt = raw_token); - ak_sys_auth_token_validate(&crt, token) + token.push_str(&raw_token); + Ok(()) } fn ak_sys_auth_token_validate(