C++: Taking a screenshot of the Windows Logon Screen/UAC Prompts without disabling UAC

2k views Asked by At

I've been looking all over the internet for an answer to this, and it just doesn't seem to be directly answered, so I thought I would ask.

Case scenario: I want to take a screenshot of what is currently on the computer screen. If it's the Windows Logon screen, I want it to be that. If it's the active user's desktop, I want to to be that. If the user elevates their application, and the UAC prompt shows up, I want it to be that.

As per lots of reading and trial and error, my current setup is as follows:

  • Program runs as a windows service
  • Gets the active user's token
  • Runs CreateProcessAsUser with the user's token to generate another instance of itself
  • Takes a screenshot and transmits it back via pipes.

Right now this is working great for a logged on user, except that screenshot is black when a UAC prompt is enabled.

Also, this method obviously won't work for getting the logon screen.

Fundamentally I am wondering how exactly does TeamViewer go about achieving this sort of thing? It is able to switch between the logon screen and a user's session flawlessly, whilst also capturing UAC prompts. I am immensely curious as to how it achieves this.

Thanks everyone!

2

There are 2 answers

2
user10530103 On BEST ANSWER

As per the advice of Davison, I have figured out how to do this, and it involves multiple steps.

Firstly, one must use CreateProcessAsUser to create a process inside the console session (obtained from WTSGetActiveConsoleSessionId). Something to note is that this process must have administrative privileges, which simply getting a handle to the user's token will not do. The way around this evidently, is to get a handle to a process running with administrative privileges, get this processes' token, duplicate it, and use that with CreateProcessAsUser. The process I used for this was Winlogon.

After this, the rest is quite simple; use OpenInputDesktop to get a handle to the desktop the user is currently seeing (it will be Default for actual desktop, and Winlogon for the UAC Prompt and login screen). After this, use SetThreadDesktop to set your processes' thread to the appropriate desktop, and capture the screen. Assuming that your Process has the privileges to create a handle to the Winlogon desktop, you will be able to capture the login screen/uac prompts and the regular user desktop.

Again, thanks to Davison, who pointed me in the right direction.

0
Michael Haephrati On

I found the optimal way to do that and summarized it in this article https://www.codeproject.com/Articles/5345258/Thank-You-for-Your-Service-Creating-a-Persistent-I

Before getting the user's token you need to wait for the user to log in, so for that, I wrote the following class:

class UserLoginListner
{
    HANDLE hWait = NULL;
    HANDLE hSubscription = NULL;

public:
    ~UserLoginListner()
    {
        CloseHandle(hWait);
        EvtClose(hSubscription);
    }

    UserLoginListner()
    {
        const wchar_t* pwsPath = EVENT_SUBSCRIBE_PATH;
        const wchar_t* pwsQuery = EVENT_SUBSCRIBE_QUERY;

        hWait = CreateEvent(NULL, FALSE, FALSE, NULL);

        hSubscription = EvtSubscribe(NULL, NULL,
            pwsPath, pwsQuery,
            NULL,
            hWait,
            (EVT_SUBSCRIBE_CALLBACK)UserLoginListner::SubscriptionCallback,
            EvtSubscribeToFutureEvents);
        if (hSubscription == NULL)
        {
            DWORD status = GetLastError();

            if (ERROR_EVT_CHANNEL_NOT_FOUND == status)
                wprinf(L"Channel %s was not found.\n", pwsPath);
            else if (ERROR_EVT_INVALID_QUERY == status)
                wprintf(L"The query \"%s\" is not valid.\n", pwsQuery);
            else
                wprintf(L"EvtSubscribe failed with %lu.\n", status);

            CloseHandle(hWait);
        }
    }
};