Reputation: 91
I am developing credential provider with custom authentication package.
I have implemented LsaApLogonUserEx() as per msdn.
My AP function LsaApLogonUserEx() gets called during logon. CreateLogonsession() is successful but immediately winlogon crashes with below stack trace.
same question was asked in msdn forum before but solution is not mentioned. Below is the link https://social.msdn.microsoft.com/Forums/en-US/c66f24bf-deaa-4196-a97e-d8b7c64f3319/custom-authentication-package-winlogonexe-exception-after-successfull-logon?forum=windowsgeneraldevelopmentissues I am trying to find out what is causing this issue. Please let me know if you know the solution.
code snippet:
PUNICODE_STRING
alloc(PWCHAR src, USHORT len)
{
PUNICODE_STRING target;
if (!(target = (PUNICODE_STRING)funcs->AllocateLsaHeap(sizeof(UNICODE_STRING))))
return NULL;
target->Length = len * sizeof(WCHAR);
target->MaximumLength = target->Length + sizeof(WCHAR);
if (!(target->Buffer = (PWSTR)funcs->AllocateLsaHeap(target->MaximumLength)))
{
funcs->FreeLsaHeap(target);
return NULL;
}
wcscpy(target->Buffer, src);
return target;
}
extern "C" NTSTATUS NTAPI
LsaApLogonUserEx(PLSA_CLIENT_REQUEST request, SECURITY_LOGON_TYPE logon_type,
PVOID auth, PVOID client_auth_base, ULONG auth_len,
PVOID * profileBuffer, PULONG pbuf_len, PLUID logon_id,
PNTSTATUS sub_stat, PLSA_TOKEN_INFORMATION_TYPE tok_type,
PVOID * tok, PUNICODE_STRING * account,
PUNICODE_STRING * authority, PUNICODE_STRING * machine)
{
writelog("LsaApLogonUserEx");
DWORD checksum, i;
PDWORD csp, csp_end;
NTSTATUS stat;
SECPKG_CLIENT_INFO clinf;
/* Check if the caller has the SeTcbPrivilege, otherwise refuse service. */
stat = funcs->GetClientInfo(&clinf);
if (stat != STATUS_SUCCESS)
{
writelog("getclientinfo failed");
return stat;
}
if (!clinf.HasTcbPrivilege)
{
writelog("no tcbprivilege");
return STATUS_ACCESS_DENIED;
}
wchar_t uname[256] = L"hari";
if (account && !(*account = alloc(uname,
wcslen(uname))))
{
return STATUS_NO_MEMORY;
}
wchar_t dname[256] = L"DESKTOP";
if (authority && !(*authority = alloc(dname,
wcslen(dname))))
{
return STATUS_NO_MEMORY;
}
if (machine)
{
WCHAR mach[MAX_COMPUTERNAME_LENGTH + 1];
DWORD msize = MAX_COMPUTERNAME_LENGTH + 1;
if (!GetComputerNameW(mach, &msize))
wcscpy(mach, L"UNKNOWN");
if (!(*machine = alloc(mach, wcslen(mach))))
{
return STATUS_NO_MEMORY;
}
}
//Prepare local buffer
wchar_t homedir[256] = L"C:\\Users\\hari";
auto homedirLength = wcslen(homedir) * sizeof(WCHAR);
auto homedirMaximumLength = homedirLength + sizeof(WCHAR);
wchar_t username[256] = L"hari";
auto fullnameLength = wcslen(username) * sizeof(WCHAR);
auto fullnameMaximumLength = fullnameLength + sizeof(WCHAR);
wchar_t drive[256] = L"C:";
auto driveLength = wcslen(drive) * sizeof(WCHAR);
auto driveMaximumLength = driveLength + sizeof(WCHAR);
wchar_t server[256] = L"DESKTOP";
auto serverLength = wcslen(server) * sizeof(WCHAR);
auto serverMaximumLength = serverLength + sizeof(WCHAR);
ULONG totalbufsize = sizeof(KERB_INTERACTIVE_PROFILE1)
+ homedirMaximumLength + fullnameMaximumLength
+ driveMaximumLength + serverMaximumLength;
//allocate local buffer
KERB_INTERACTIVE_PROFILE* pLocalBuf;
pLocalBuf = (KERB_INTERACTIVE_PROFILE*)funcs->AllocateLsaHeap(totalbufsize);
//allocate client buffer
if (profileBuffer)
{
stat = funcs->AllocateClientBuffer(request, totalbufsize, profileBuffer);
if (!LSA_SUCCESS(stat))
{
writelog("buffer alloc failed");
return stat;
}
}
if (pbuf_len)
*pbuf_len = totalbufsize;
//construct local buffer with client pointers
pLocalBuf->MessageType = KerbInteractiveProfile;
pLocalBuf->LogonCount = 0;
pLocalBuf->BadPasswordCount = 0;
pLocalBuf->LogonScript.Length = 0;
pLocalBuf->LogonScript.Buffer = NULL;
pLocalBuf->HomeDirectory.Length = wcslen(homedir) * sizeof(WCHAR);
pLocalBuf->HomeDirectory.MaximumLength = pLocalBuf->HomeDirectory.Length + sizeof(WCHAR);
pLocalBuf->FullName.Length = wcslen(username) * sizeof(WCHAR);
pLocalBuf->FullName.MaximumLength = pLocalBuf->FullName.Length + sizeof(WCHAR);
pLocalBuf->HomeDirectoryDrive.Length = wcslen(drive) * sizeof(WCHAR);
pLocalBuf->HomeDirectoryDrive.MaximumLength = pLocalBuf->HomeDirectoryDrive.Length + sizeof(WCHAR);
pLocalBuf->LogonServer.Length = wcslen(server) * sizeof(WCHAR);
pLocalBuf->LogonServer.MaximumLength = pLocalBuf->LogonServer.Length + sizeof(WCHAR);
pLocalBuf->ProfilePath.Length = 0;
pLocalBuf->ProfilePath.Buffer = NULL;
pLocalBuf->UserFlags = LOGON_RESOURCE_GROUPS;
PBYTE pLocalpointer;
pLocalpointer = (PBYTE)(pLocalBuf + 1);
wcscpy((wchar_t*)pLocalpointer, homedir);
pLocalpointer += pLocalBuf->HomeDirectory.MaximumLength;
wcscpy((wchar_t*)pLocalpointer, username);
pLocalpointer += pLocalBuf->FullName.MaximumLength;
wcscpy((wchar_t*)pLocalpointer, drive);
pLocalpointer += pLocalBuf->HomeDirectoryDrive.MaximumLength;
wcscpy((wchar_t*)pLocalpointer, server);
PBYTE pkip = (PBYTE)(((KERB_INTERACTIVE_PROFILE1*)*profileBuffer) + 1);
pLocalBuf->HomeDirectory.Buffer = (PWSTR)pkip;
pkip += pLocalBuf->HomeDirectory.MaximumLength;
pLocalBuf->FullName.Buffer = (PWSTR)pkip;
pkip += pLocalBuf->FullName.MaximumLength;
pLocalBuf->HomeDirectoryDrive.Buffer = (PWSTR)pkip;
pkip += pLocalBuf->HomeDirectoryDrive.MaximumLength;
pLocalBuf->LogonServer.Buffer = (PWSTR)pkip;
//copy local buffer to client buffer
auto status2 = funcs->CopyToClientBuffer(request, totalbufsize, *profileBuffer, (PVOID)pLocalBuf);
char strstat1[256];
sprintf(strstat1, "copyto status: 0x%08lx", status2);
writelog(strstat1);
funcs->FreeLsaHeap((PVOID)pLocalBuf);
//create token
char sidlocal[256] = "S-1-2-0";
PSID psidlocal;
auto res = ConvertStringSidToSidA(sidlocal, &psidlocal);
auto sizelocalsid = GetLengthSid(psidlocal);
char sidhari[256] = ""; //hardcoded user sid for testing purpose
PSID psidhari;
auto res1 = ConvertStringSidToSidA(sidhari, &psidhari);
auto sizeharisid = GetLengthSid(psidhari);
PLSA_TOKEN_INFORMATION_V2 tokinf;
if (!(tokinf = (PLSA_TOKEN_INFORMATION_V2)funcs->AllocateLsaHeap(sizeof(LSA_TOKEN_INFORMATION_V2) + sizelocalsid
+ sizeof(TOKEN_GROUPS) + sizeharisid)))
{
writelog("Failed to allocate memory for tokinf");
return STATUS_NO_MEMORY;
}
PTOKEN_GROUPS grps;
PBYTE tptr;
tptr = (PBYTE)(tokinf + 1);
//Expiration time
SYSTEMTIME st;
FILETIME ft;
double seconds = 3600;
GetLocalTime(&st);
SystemTimeToFileTime(&st, &ft);
((ULARGE_INTEGER*)&ft)->QuadPart += (seconds * 10000000LLU);
LARGE_INTEGER u;
memcpy(&u, &ft, sizeof(u));
tokinf->ExpirationTime.LowPart = u.LowPart;//0xffffffffL;
tokinf->ExpirationTime.HighPart = u.HighPart;// 0xffffffffL;
CopySid(sizelocalsid, (PSID)tptr, psidlocal);
tokinf->User.User.Sid = (PSID)tptr;
tptr += sizelocalsid;
tokinf->User.User.Attributes = SE_GROUP_LOGON_ID;
grps = (PTOKEN_GROUPS)tptr;
tokinf->Groups = grps;
grps->GroupCount = 0;
tokinf->Groups->Groups[0].Attributes = SE_GROUP_LOGON_ID;
tptr += sizeof(TOKEN_GROUPS);
CopySid(sizeharisid, (PSID)tptr, psidhari);
tokinf->PrimaryGroup.PrimaryGroup = (PSID)tptr;
tptr += sizeharisid;
tokinf->Privileges = NULL;
tokinf->Owner.Owner = NULL;
tokinf->DefaultDacl.DefaultDacl = NULL;
*tok = (PVOID)tokinf;
*tok_type = LsaTokenInformationV2;
stat = funcs->CreateLogonSession(logon_id);
if (stat != STATUS_SUCCESS)
{
funcs->FreeLsaHeap(*tok);
*tok = NULL;
writelog("create logon failed");
return stat;
}
LocalFree(psidlocal);
LocalFree(psidhari);
writelog("logon success");
return STATUS_SUCCESS;
}
Thanks, Hari
Upvotes: 3
Views: 570
Reputation: 186
Not sure what is wrong about your last version of the code, it seems fine to me, so here's mine which is working. I am using a MSV1_0_INTERACTIVE_PROFILE structure instead of a KERB_INTERACTIVE_PROFILE but they seems to be the same except for the MessageType. I am also using correct data retrieved from NetUserGetInfo but it was already working before I did that.
I also can't help notice your authenticating authority is "DESKTOP", is that the real name ? From my experience using an incorrect name there can cause a crash.
template<typename T>
T PointerToOffset(T pointer, void* base)
{
return (pointer == nullptr) ? nullptr : reinterpret_cast<T>(reinterpret_cast<ULONG_PTR>(pointer) - reinterpret_cast<ULONG_PTR>(base));
}
template<typename T>
T OffsetToPointer(T offset, void* base)
{
return (reinterpret_cast<ULONG_PTR>(offset) <= sizeof(T)) ? nullptr : reinterpret_cast<T>(reinterpret_cast<ULONG_PTR>(offset) + reinterpret_cast<ULONG_PTR>(base));
}
size_t InitSerializedUnicodeString(UNICODE_STRING& unicode, const wchar_t* str,
BYTE* bufferPos)
{
size_t ccLen = wcslen(str);
unicode.Length = static_cast<USHORT>(ccLen * sizeof(WCHAR));
unicode.MaximumLength = static_cast<USHORT>((ccLen + 1) * sizeof(WCHAR));
unicode.Buffer = reinterpret_cast<PWSTR>(bufferPos);
if (memcpy_s(unicode.Buffer, unicode.MaximumLength, str, ccLen * sizeof(WCHAR)) != 0)
return 0;
unicode.Buffer[ccLen] = L'\0';
return unicode.MaximumLength;
}
NTSTATUS CreateMSV1Profile(const std::wstring& username, const std::wstring& authenticatingAuthority,PLSA_CLIENT_REQUEST pClientRequest, PVOID* ppProfileBuffer, PULONG pcbProfileBuffer)
{
NTSTATUS status = STATUS_SUCCESS;
BYTE* userInfoBuffer = nullptr;
NET_API_STATUS netStatus = NetUserGetInfo(NULL, username.c_str(), 2, &userInfoBuffer);
if (netStatus != NERR_Success)
{
Logger::Error(L"Cannot retrieve user info, NetUserGetInfo error {:#x} ({:d})", netStatus, netStatus);
return netStatus;
}
std::unique_ptr<BYTE, decltype(&NetApiBufferFree)> raiiUserInfo(userInfoBuffer, NetApiBufferFree);
USER_INFO_2* userInfo = reinterpret_cast<USER_INFO_2*>(userInfoBuffer);
// Allocating profile in one memory block, in packed format. Mimicking how the MSV1 auth package does it.
ULONG cbProf = static_cast<ULONG>(sizeof(MSV1_0_INTERACTIVE_PROFILE) + (username.size() + authenticatingAuthority.size() + 2) * sizeof(wchar_t));
std::vector<BYTE> profBuffer(cbProf, 0);
if (ppProfileBuffer)
{
MSV1_0_INTERACTIVE_PROFILE* prof = reinterpret_cast<MSV1_0_INTERACTIVE_PROFILE*>(&profBuffer[0]);
prof->MessageType = MsV1_0InteractiveProfile;
prof->LogonCount = static_cast<USHORT>(userInfo->usri2_num_logons + 1);
prof->BadPasswordCount = static_cast<USHORT>(userInfo->usri2_bad_pw_count);
prof->LogonTime.QuadPart = 0;
prof->LogoffTime.QuadPart = MAXLONGLONG;
prof->KickOffTime.QuadPart = MAXLONGLONG;
prof->PasswordLastSet.QuadPart = userInfo->usri2_password_age;
prof->PasswordCanChange.QuadPart = (userInfo->usri2_flags | UF_PASSWD_CANT_CHANGE) ? MAXLONGLONG : 0;
prof->PasswordMustChange.QuadPart = MAXLONGLONG;
prof->UserFlags = LOGON_EXTRA_SIDS;
prof->LogonScript = { 0 };
prof->HomeDirectory = { 0 };
prof->FullName = { 0 };
prof->ProfilePath = { 0 };
prof->HomeDirectoryDrive = { 0 };
prof->LogonServer = { 0 };
BYTE* bufferWritingPos = reinterpret_cast<BYTE*>(prof + 1);
bufferWritingPos += InitSerializedUnicodeString(prof->FullName, username.c_str(), bufferWritingPos);
bufferWritingPos += InitSerializedUnicodeString(prof->LogonServer, authenticatingAuthority.c_str(), bufferWritingPos);
status = LsaMemory::_dispatchTable->AllocateClientBuffer(pClientRequest, cbProf, ppProfileBuffer);
if (!LSA_SUCCESS(status))
{
Logger::Error(L"AllocateClientBuffer failed {:s}", GetWinErrorString(LsaNtStatusToWinError(status)));
return status;
}
// Use pointers relative to client buffer
prof->FullName.Buffer = PointerToOffset<PWSTR>(prof->FullName.Buffer, prof);
prof->LogonServer.Buffer = PointerToOffset<PWSTR>(prof->LogonServer.Buffer, prof);
prof->FullName.Buffer = OffsetToPointer<PWSTR>(prof->FullName.Buffer, *ppProfileBuffer);
prof->LogonServer.Buffer = OffsetToPointer<PWSTR>(prof->LogonServer.Buffer, *ppProfileBuffer);
if (pcbProfileBuffer)
*pcbProfileBuffer = cbProf;
status = LsaMemory::_dispatchTable->CopyToClientBuffer(pClientRequest, cbProf, *ppProfileBuffer, prof);
if (!LSA_SUCCESS(status))
{
Logger::Error(L"CopyToClientBuffer failed {:s}", GetWinErrorString(LsaNtStatusToWinError(status)));
return status;
}
}
else if (pcbProfileBuffer)
{
*pcbProfileBuffer = 0;
}
return status;
}
Upvotes: 2