4

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. enter image description here

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

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
HNR
  • 101
  • 1
  • 4
  • you wrong copy `ProfileBuffer` to winlogon – RbMm Apr 05 '23 at 11:35
  • 1
    @RbMm Can you please elaborate bit more. I have allocated 64 bytes for profile buffer. Is there anything wrong here? – HNR Apr 05 '23 at 12:06
  • @Jabberwocky I have added code snippet where Accountname, Authority, Machine and profilebuffer are filled. If required , I will add code for preparing token. – HNR Apr 05 '23 at 12:12
  • Not quite sure what `alloc` is but maybe `alloc(uname, wcslen(uname))` -> `alloc(uname, wcslen(uname) + 1)` ? – Jabberwocky Apr 05 '23 at 12:13
  • @Jabberwocky Yes. I have added alloc func. – HNR Apr 05 '23 at 12:17
  • not visible in code how you allocate, initialize and copy to caller ProfileBuffer – RbMm Apr 05 '23 at 15:48
  • @RbMm This is the code for profilebuffer allocation and specifying length.if (pbuf) { stat = funcs->AllocateClientBuffer(request, 64, pbuf); if (!LSA_SUCCESS(stat)) { return stat; } } if (pbuf_len) *pbuf_len = 64; – HNR Apr 05 '23 at 16:12
  • 1
    and where is CopyToClientBuffer ? – RbMm Apr 05 '23 at 16:33
  • @RbMm Not aware of that. can you please tell me what should be copied? – HNR Apr 05 '23 at 17:09
  • AllocateClientBuffer() allocates in clients address space. Do we need to copy any specific data using CopyToClientBuffer() ? Just tried adding char buf[64]; memset(buf, 'a', 64); funcs->CopyToClientBuffer(request, 64, *pbuf, buf); still exception occurs with below call stack: msvcrt.dll!memcpy() winlogon.exe!NotifySetEnvironmentVariableUnicode() winlogon.exe!CUser::CreatePreNotifyEnvironment(unsigned short **) – HNR Apr 06 '23 at 02:08
  • 1
    are you still not understand that simply allocate memory or copy trash to it - wrong and result exception ? you must format valid (in client memory !) `KERB_INTERACTIVE_PROFILE` or `KERB_SMART_CARD_PROFILE` – RbMm Apr 06 '23 at 13:13
  • @RbMm Thanks for the help. I have modified code to fill profilebuffer with KERB_INTERACTIVE_PROFILE. exception still persists. Can you please check my code above whether I am doing it in correct way. To eliminate structure padding issue, I have defined same struct locally as KERB_INTERACTIVE_PROFILE1 with #pragma pack(push,1) – HNR Apr 07 '23 at 11:25
  • you must call AllocateClientBuffer only once. and CopyToClientBuffer once. the `KERB_INTERACTIVE_PROFILE` must be in single memory block, because it must be free in signle call `LsaFreeReturnBuffer`. you first need calc required size, allocate this memory, format structure, with pointers valid in client address space, not your , and copy it – RbMm Apr 07 '23 at 12:55
  • @RbMm I have updated code to allocate it as single block and constructed local buffer with client pointers and then copied to client buffer. I have placed the code above. With this change, there is an exception in sihost.exe with exception code 0xC0000409. It looks like there is problem with profilebuffer length (*pbuf_len = totalbufsize;). When I modify the length to 64, it is back to old problem. I don't understand what is wrong with profilebuffer length. is there any setting that needs to be done – HNR Apr 08 '23 at 12:32
  • Hello @HarinathaReddy, could you please show a minimal, reproducible sample without private information? – YangXiaoPo-MSFT Apr 14 '23 at 09:07
  • 1
    @RbMm Thank you so much for the help. It is working now. The mistake I did is, allocated local buffer with KERB_INTERACTIVE_PROFILE and profilebuffer with KERB_INTERACTIVE_PROFILE1. This mismatch led to crash. I have defined KERB_INTERACTIVE_PROFILE1 locally to avoid structure padding. I found the issue when I tested my AP with LsaLogonUser(). Thanks again. – HNR Apr 14 '23 at 12:01

1 Answers1

2

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;
}
Nehluxhes
  • 166
  • 2
  • 9
  • Thanks for the code. I have tried it but issue remains. I have placed my LsaApLogonUserEx() here. Can you please check if there is anything wrong. yes, workstation name is Desktop. I have hardcoded usersid for creation of token. During logon, this function completes successfully. I see the logs with "logon success". I think when winlogon tries to access the buffer, there is something wrong. There is exception in sihost.exe with exception code 0xC0000409. I just tried reducing buffer to 100 bytes with random data, then winlogon allocservername exception occurs. – HNR Apr 13 '23 at 08:33
  • Is there anything we have to do any credential provider apart from specifying authentication package? authentication information from CP is not used currently in AP. I am using sample credential provider. is there anything that determines winlogon profile buffer length in CP? – HNR Apr 13 '23 at 08:37
  • 1
    You don't have to do anything special in the CP, but winlogon indeed seems to expect one of the built-in profile structures, I struggled with this too. I don't know what your objectives are, but unless you want to replace the existing authentication system you don't have to develop your own AP, you can just use a built-in one. If you just want to add a second factor like most people do you can do your check in the CP and it will be much easier. I frankly found my AP really difficult to write and it took me a lot of time (and I am not finished). – Nehluxhes Apr 13 '23 at 09:45
  • As for your code well first you are not creating a unique LUID with AllocateLocallyUniqueId, that's your AP responsability. And lots of info is missing from your token, no privileges at all won't work to begin with. If you want to go that route I suggest you first try to do a successful logon calling LsaLogonUser in a separate executable, then when that work you try with winlogon. Winlogon has harsher requirements. – Nehluxhes Apr 13 '23 at 09:46
  • AllocateLocallyUniqueId() was there initially but during several experiments it got missed. I will add it. Our objective is to have password less authentication. Authentication will be through a token. So, I thought this is the only way it is achieved. ok. I will try LsaLogonUser. Thanks. If there is anything wrong with token, I think LSA will complain right? Regarding what profile buffer winlogon expects, is it documented anywhere? – HNR Apr 13 '23 at 10:21
  • I just saw your comment in other question related to custom AP not loaded. In my case, my AP is not loaded without sp* functions and my AP name is in LSA\Security Packages not Authentication Packages. Initially I tried with AP in LSA\Authentication Packages and exporting def file,but didn't work. – HNR Apr 13 '23 at 10:31
  • 1
    Yeah it will complain... by crashing :) My development time was 80% trial&error. And to my knowledge no the winlogon expected profile output buffers are not documented anywhere. You are supposed to be able to return anything you want according to the AP doc, and you can retrieve your buffer using LsaLogonUser, but in the specific case of winlogon only MSV1 and KERB structures seems to work. – Nehluxhes Apr 13 '23 at 11:14
  • I initially tried to just return a null or a few bytes (like in this accepted answer https://stackoverflow.com/questions/33367374/custom-windows-authentication-package-logon-failed?rq=1) and it was crashing, and I basically wasted weeks trying to debug my token structure when the mistake was not returning a proper profile buffer. – Nehluxhes Apr 13 '23 at 11:15
  • 1
    Thank you so much for the help. It is working now. The mistake I did is, allocated local buffer with KERB_INTERACTIVE_PROFILE and profilebuffer with KERB_INTERACTIVE_PROFILE1. This mismatch led to crash. I have defined KERB_INTERACTIVE_PROFILE1 locally to avoid structure padding. I found the issue when I tested my AP with LsaLogonUser(). You have suggested me to check with LsaLogonUser() first. That helped. Thanks again. – HNR Apr 14 '23 at 12:03