CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 13 of 13
  1. #1
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Code review request for displaying UI over the logon screen

    Hi everyone:

    I have a Windows service written in C++/WinAPI that runs in a local-system context. The service may perform some configurable power actions (such as put the system into sleep mode) depending on user inactivity and other parameters related to my app. But before performing these actions I would like to show a user warning in a form of a simple UI window with a countdown.

    Displaying such warning UI when an interactive user is logged on to the workstation is not an issue and is outside of the scope of this question.

    The issue comes up when I need to display my warning UI over a logon screen in 2 basic scenarios:

    1. When there's no logged on interactive users (say, when the system just boots up and before any user had a chance to log in.)

    2. When the workstation is locked by a user, either by selecting Start -> (Power Button) -> Lock workstation, or by hitting Ctrl+Alt+Delete on the keyboard, etc.

    I was able to come up with the following code (that is called from within my service) that works well in the (1) scenario. Just as a proof of concept I'm using Windows calculator in place of my user-mode process that would normally display the warning UI. It produces something like this:

    Name:  CfZiA.png
Views: 1373
Size:  85.1 KB

    Here's the pseudo-code that I'm using:

    Code:
    HANDLE hSelfToken = NULL;
    OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
    
    HANDLE hTokenNew = NULL;
    DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, NULL, SecurityIdentification, TokenPrimary, &hTokenNew);
    
    //I'm not sure about the need of this duplication???
    HANDLE hToken2 = NULL;
    DuplicateHandle(::GetCurrentProcess(), hTokenNew, ::GetCurrentProcess(), &hToken2, 0, FALSE, DUPLICATE_SAME_ACCESS));
    
    LPVOID pEnvBlock = NULL;
    CreateEnvironmentBlock(&pEnvBlock, hToken2, FALSE);
    
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    
    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));
    
    TCHAR pBuffCmdLine[MAX_PATH];
    pBuffCmdLine[0] = 0;
    
    StringCchCopy(pBuffCmdLine, MAX_PATH, L"calc.exe"); //As a test launch the calculator
    
    ImpersonateLoggedOnUser(hToken2);
    
    bResult = !!CreateProcessAsUser(
            hToken2,            // client's access token
            L"calc.exe",        // file to execute
            pBuffCmdLine[0] != 0 ? pBuffCmdLine : NULL,     // command line
            NULL,              // pointer to process SECURITY_ATTRIBUTES
            NULL,              // pointer to thread SECURITY_ATTRIBUTES
            FALSE,             // handles are not inheritable
            NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,   // creation flags
            pEnvBlock,              // pointer to new environment block 
            NULL,              // name of current directory 
            &si,               // pointer to STARTUPINFO structure
            &pi                // receives information about new process
        );
    
    RevertToSelf();
    
    if(bResult)
    {
        //Can wait on process handle in 'pi.hProcess' if needed...
    }
    
    if(pi.hProcess)
        CloseHandle(pi.hProcess);
    if(pi.hThread)
        CloseHandle(pi.hThread);
    
    if(pEnvBlock)
        DestroyEnvironmentBlock(pEnvBlock);
    
    CloseHandle(hToken2);
    CloseHandle(hTokenNew);
    CloseHandle(hSelfToken);
    But the issue with the code above is that it does not fully work in the (2) scenario, or when my 'dwActvSessID' variable is 1 and up, or when there's a logged in interactive session. What happens in that case is that the calc.exe process starts up and runs but it's UI is not visible above the logon desktop UI. Here's the screenshot from the TaskManager, and as you see the calc.exe is running, in this case as a SYSTEM account:

    Name:  3.png
Views: 1303
Size:  39.5 KB

    There's a definitely some mix-up with the desktop that my UI process is started under.

    Any idea what am I missing here?


    PS. Just as an interesting observation, if I replace "calc.exe" with any non-GUI process, such as "cmd.exe" its window does show up above the logon screen, as I would expect it. Here's a screenshot:

    Name:  2.jpg
Views: 1253
Size:  25.8 KB
    Last edited by dc_2000; July 16th, 2015 at 10:27 AM.

  2. #2
    Join Date
    Jul 2002
    Posts
    2,543

    Re: Code review request for displaying UI over the logon screen

    Just a thought: cmd.exe itself is a GUI process, like calc.exe. The difference is that it is written without .NET, WPF and other modern Windows crap.

  3. #3
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Re: Code review request for displaying UI over the logon screen

    Quote Originally Posted by Alex F View Post
    Just a thought: cmd.exe itself is a GUI process, like calc.exe. The difference is that it is written without .NET, WPF and other modern Windows crap.
    Well, I thought so too. I built a simple (stock) Win32 GUI app and it did not show either. In other words, it's not the issue with the child GUI process that I'm running... it's something else in the way I run it from my service.

  4. #4
    Join Date
    Jul 2002
    Posts
    2,543

    Re: Code review request for displaying UI over the logon screen

    What about desktop Win32 application?

  5. #5
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Re: Code review request for displaying UI over the logon screen

    Quote Originally Posted by Alex F View Post
    What about desktop Win32 application?
    Yes, that's what I mean. It did the same thing as the calc.exe

  6. #6
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: Code review request for displaying UI over the logon screen

    Have you tried to create a Win32 dialog app with the style of 'always on top' and a null parent hWnd?

    As an alternative, can you get the hWnd of the locked screen and use it as your dialog's parent hWnd?
    Last edited by Arjay; July 12th, 2015 at 06:58 PM.

  7. #7
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Re: Code review request for displaying UI over the logon screen

    Thanks, Arjay.

    Quote Originally Posted by Arjay View Post
    Have you tried to create a Win32 dialog app with the style of 'always on top' and a null parent hWnd?
    No, I haven't. The issue is that we're talking about two different desktops here. The one on the lock screen is what they refer to as a "Secure desktop" or "Winlogon", while the desktop that is used by all user-mode apps is simply named as "Default."

    Quote Originally Posted by Arjay View Post
    As an alternative, can you get the hWnd of the locked screen and use it as your dialog's parent hWnd?
    So even if I manage to get a handle from the locked screen it will not be valid in another desktop, and there's no API, that I know of, similar to DuplicateHandle that can duplicate a window handle from one desktop to another.

    The only "hacky" way to go here is to inject my code into the system process that displays the logon screen (or LogonUI.exe) to call CreateProcess API for my user process. I'm sure that would produce the result I want, but I'm not really a big fan of injecting anything into secure OS components. I want to try first to do it in a less intrusive way...

  8. #8
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Re: Code review request for displaying UI over the logon screen

    While waiting for your responses, I did some research and found this MSDN sample that shows how to configure the ACL for the window station and the desktop. I originally didn't know that a window station and a desktop were securable objects. So I was very happy to incorporate that code in my sample above.

    Unfortunately it didn't work. Let me explain.

    The part where they obtain a logon SID from the user token, or GetLogonSid() method, where in my case I'd call it as such:

    Code:
    //..previous code -- see above
    
    TCHAR pBuffCmdLine[MAX_PATH];
    pBuffCmdLine[0] = 0;
    
    StringCchCopy(pBuffCmdLine, MAX_PATH, L"calc.exe"); //As a test launch the calculator
    
    //BEGIN ADDED CODE////////////////////////////////////////////
    
    // Save a handle to the caller's current window station.
    
       if ( (hwinstaSave = GetProcessWindowStation() ) == NULL)
          goto Cleanup;
    
    // Get a handle to the interactive window station.
    
       hwinsta = OpenWindowStation(
           _T("winsta0"),                   // the interactive window station 
           FALSE,                       // handle is not inheritable
           READ_CONTROL | WRITE_DAC);   // rights to read/write the DACL
    
       if (hwinsta == NULL) 
          goto Cleanup;
    
    // To get the correct default desktop, set the caller's 
    // window station to the interactive window station.
    
       if (!SetProcessWindowStation(hwinsta))
          goto Cleanup;
    
    // Get a handle to the interactive desktop.
    
       hdesk = OpenDesktop(
          _T("Winlogon"),     // the interactive window station  --- [sp?] Should've said "desktop" and not "window station" -- I ALSO HAD TO CHANGE IT TO "Winlogon"
          0,             // no interaction with other desktop processes
          FALSE,         // handle is not inheritable
          READ_CONTROL | // request the rights to read and write the DACL
          WRITE_DAC | 
          DESKTOP_WRITEOBJECTS | 
          DESKTOP_READOBJECTS);
    
    // Restore the caller's window station.
    
       if (!SetProcessWindowStation(hwinstaSave)) 
          goto Cleanup;
    
       if (hdesk == NULL) 
          goto Cleanup;
    
    // Get the SID for the client's logon session.
    
       if (!GetLogonSID(hToken2, &pSid)) 
          goto Cleanup;
    
    // Allow logon SID full access to interactive window station.
    
       if (! AddAceToWindowStation(hwinsta, pSid) ) 
          goto Cleanup;
    
    // Allow logon SID full access to interactive desktop.
    
       if (! AddAceToDesktop(hdesk, pSid) ) 
          goto Cleanup;
    
    // Impersonate client to ensure access to executable file.
    
    //END ADDED CODE////////////////////////////////////////////
    
    ImpersonateLoggedOnUser(hToken2);
    
    //...further code -- see above
    Their GetLogonSID method has a bug, or where they put bSuccess = TRUE; outside of the for() loop without actually filling in the PSID *ppsid variable, so in my case that method does not find a logon SID in my token and returns ppsid as NULL and TRUE from the method itself. This causes the subsequent calls using the NULL logon SID pointer, namely GetLengthSid() and AddAccessAllowedAce(), to crash. (But I'm sure that they were not intending to receive a NULL logon SID anyway.)

    In either case, I understand why I'm getting a NULL logon SID. That's because my user token comes from my local service running under the SYSTEM credentials, which does not have a logon SID. I just changed the session ID in it with the SetTokenInformation() API.

    So, I'm now trying to understand what exactly do their AddAceToWindowStation() and AddAceToDesktop() methods do? And more specifically where do they take those ACEs from to adjust it for my situation, or what shall I pass as the psid parameter into those methods?

    Any idea on that?
    Last edited by dc_2000; July 13th, 2015 at 12:53 AM.

  9. #9
    Join Date
    Mar 2001
    Posts
    2,529

    Re: Code review request for displaying UI over the logon screen

    Ouch, a real live goto statement...?!???
    ahoodin
    To keep the plot moving, that's why.

  10. #10
    Arjay's Avatar
    Arjay is offline Moderator / EX MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    13,490

    Re: Code review request for displaying UI over the logon screen

    Quote Originally Posted by dc_2000 View Post
    Thanks, Arjay.


    No, I haven't. The issue is that we're talking about two different desktops here. The one on the lock screen is what they refer to as a "Secure desktop" or "Winlogon", while the desktop that is used by all user-mode apps is simply named as "Default."
    Give it a try. It's easy enough to do so and maybe it will work (even though they are different desktops).

  11. #11
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Re: Code review request for displaying UI over the logon screen

    Quote Originally Posted by ahoodin View Post
    Ouch, a real live goto statement...?!???
    Yep, and it didn't come from me. It's MSDN. Plus, a buggy code too. (See my explanation of their GetLogonSid() method.)

  12. #12
    Join Date
    Mar 2001
    Posts
    2,529

    Re: Code review request for displaying UI over the logon screen

    Quote Originally Posted by dc_2000 View Post
    Yep, and it didn't come from me. It's MSDN. Plus, a buggy code too. (See my explanation of their GetLogonSid() method.)
    Cover mouth, looking like blowfish, what's going on? Uh yeah, time to refactor.
    ahoodin
    To keep the plot moving, that's why.

  13. #13
    Join Date
    Nov 2003
    Location
    Portland, OR
    Posts
    894

    Re: Code review request for displaying UI over the logon screen

    Quote Originally Posted by Arjay View Post
    Give it a try. It's easy enough to do so and maybe it will work (even though they are different desktops).
    Arjay, you're the man! It worked!

    All it needed is for my child GUI process to be created with the WS_EX_TOPMOST style. (This obviously wouldn't work with the stock calc.exe.) Evidently the logon UI process creates its logon GUI with the top-most style when the workstation is locked, and does not use that style when it's a plain logon UI. And in case of the calc.exe it was running in the correct desktop, it was just obscured by the top-most logon GUI. Who would've thunk it?

    Anyway, appreciate your being insistent on trying it out. I was so sure that it wouldn't work ... anyway, the most excitement comes from something that you least expected.

    PS. By the way, I don't see any need to change the ACL for the "winlogon" workstation and desktop as I showed here. So disregard that complexity.

    But as a side question, I'm curious now why Microsoft developers went an extra length to introduce that in their sample?
    Last edited by dc_2000; July 13th, 2015 at 02:24 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured