WPF application in secure desktop

This article demonstrate creating and launching WPF application in it's own desktop in a secure manner.

Windows 2000 and later versions provides set of API that supports creating, opening and switching desktops. Quick look at this link will provide you those functions and details about them:

https://msdn.microsoft.com/en-us/library/windows/desktop/ms687107(v=vs.85).aspx

Few days back, I developed one kiosk application which was supposed to run on Windows 8.1 in full screen mode at public kiosk machines. One of the requirement was to load the application in it’s desktop that will not show any taskbar or side bar and access to any other OS’s functions. Quick search on Bing landed me to this CodeProject article which gave me managed wrapper over this unmanaged APIs to access various functions easily.

I borrowed some code from this article and made my simple DesktopAPI class as following:

using System;
using System.Runtime.InteropServices;

namespace TestLauncher
{
    public class DesktopAPI
    {
         #region Constants

        private const uint INFINITE = 0xFFFFFFFF;
         private const int NORMAL_PRIORITY_CLASS = 0x00000020;

         #endregion

        #region Enums

        private enum DESKTOP_ACCESS : uint
        {
            DESKTOP_NONE = 0,

            DESKTOP_READOBJECTS = 0x0001,

            DESKTOP_CREATEWINDOW = 0x0002,

            DESKTOP_CREATEMENU = 0x0004,

            DESKTOP_HOOKCONTROL = 0x0008,

            DESKTOP_JOURNALRECORD = 0x0010,

            DESKTOP_JOURNALPLAYBACK = 0x0020,

            DESKTOP_ENUMERATE = 0x0040,

            DESKTOP_WRITEOBJECTS = 0x0080,

            DESKTOP_SWITCHDESKTOP = 0x0100,

            GENERIC_ALL =
                DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL
                | DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK | DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS
                | DESKTOP_SWITCHDESKTOP,
        }

        #endregion

        #region Public Methods and Operators

        public static void ShowSecureDesktop()
        {
            IntPtr hOldDesktop = GetThreadDesktop(GetCurrentThreadId());

            IntPtr hNewDesktop = CreateDesktop(
                 "TestAppDesktop",
                 IntPtr.Zero,
                 IntPtr.Zero,
                 0,
                 (uint)DESKTOP_ACCESS.GENERIC_ALL,
                 IntPtr.Zero);

            SwitchDesktop(hNewDesktop);

            SetThreadDesktop(hNewDesktop);

            var si = new STARTUPINFO();
            si.cb = Marshal.SizeOf(si);
            si.lpDesktop = "TestAppDesktop";

            var pi = new PROCESS_INFORMATION();

             CreateProcess(
                 null,
                 @"TestApp.exe",
                 IntPtr.Zero,
                 IntPtr.Zero,
                 true,
                 NORMAL_PRIORITY_CLASS,
                 IntPtr.Zero,
                 null,
                ref si,
                ref pi);

            WaitForSingleObject(pi.hProcess, INFINITE);

             SwitchDesktop(hOldDesktop);

             CloseDesktop(hNewDesktop);
        }

         #endregion

        #region Methods

        [DllImport("user32.dll")]
        private static extern bool CloseDesktop(IntPtr handle);

         [DllImport("user32.dll")]
        private static extern IntPtr CreateDesktop(
            string lpszDesktop,
            IntPtr lpszDevice,
            IntPtr pDevmode,
            int dwFlags,
            uint dwDesiredAccess,
            IntPtr lpsa);

        [DllImport("kernel32.dll")]
        private static extern bool CreateProcess(
            string lpApplicationName,
            string lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            int dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            ref PROCESS_INFORMATION lpProcessInformation);

         [DllImport("kernel32.dll")]
        private static extern int GetCurrentThreadId();

         [DllImport("user32.dll")]
        private static extern IntPtr GetThreadDesktop(int dwThreadId);

         [DllImport("user32.dll")]
        private static extern bool SetThreadDesktop(IntPtr hDesktop);

         [DllImport("user32.dll")]
        private static extern bool SwitchDesktop(IntPtr hDesktop);

         [DllImport("kernel32.dll", SetLastError = true)]
        private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        #endregion

        #region Structure

        [StructLayout(LayoutKind.Sequential)]
        private struct PROCESS_INFORMATION
        {
            public readonly IntPtr hProcess;

             public readonly IntPtr hThread;

             public readonly int dwProcessId;

             public readonly int dwThreadId;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct STARTUPINFO
        {
            public int cb;

             public readonly string lpReserved;

             public string lpDesktop;

             public readonly string lpTitle;

             public readonly int dwX;

             public readonly int dwY;

             public readonly int dwXSize;

             public readonly int dwYSize;

             public readonly int dwXCountChars;

             public readonly int dwYCountChars;

             public readonly int dwFillAttribute;

             public readonly int dwFlags;

             public readonly short wShowWindow;

             public readonly short cbReserved2;

             public readonly IntPtr lpReserved2;

             public readonly IntPtr hStdInput;

             public readonly IntPtr hStdOutput;

             public readonly IntPtr hStdError;
        }

        #endregion
    }
}

Listing 1.0 Desktop API wrapper



Figure 1.0 Test Launcher app


In the supplied sample code, I have put the class mentioned in Listing 1.0 in to TestLauncher project. The method to put attention to is ShowSecureDesktop.

public static void ShowSecureDesktop()
        {
            IntPtr hOldDesktop = GetThreadDesktop(GetCurrentThreadId());

            IntPtr hNewDesktop = CreateDesktop(
                 "TestAppDesktop",
                 IntPtr.Zero,
                 IntPtr.Zero,
                 0,
                 (uint)DESKTOP_ACCESS.GENERIC_ALL,
                 IntPtr.Zero);

            SwitchDesktop(hNewDesktop);

            SetThreadDesktop(hNewDesktop);

            var si = new STARTUPINFO();
            si.cb = Marshal.SizeOf(si);
            si.lpDesktop = "TestAppDesktop";

            var pi = new PROCESS_INFORMATION();

             CreateProcess(
                 null,
                 @"TestApp.exe",
                 IntPtr.Zero,
                 IntPtr.Zero,
                 true,
                 NORMAL_PRIORITY_CLASS,
                 IntPtr.Zero,
                 null,
                ref si,
                ref pi);

            WaitForSingleObject(pi.hProcess, INFINITE);

             SwitchDesktop(hOldDesktop);

             CloseDesktop(hNewDesktop);
        }

Listing 1.1 ShowSecureDesktop method


Figure 1.1 Test app loaded in secure desktop


This method first gets reference to current desktop. This is required since once your newly created desktop is closed, you need to revert back to your original desktop. We then create new desktop by calling CreateDesktop.  We will then use well known CreateProcess method to launch our test WPF app “TestApp.exe”. With this, we are also telling process to load this app into desktop named “TestAppDesktop”. This is the desktop which we just created. So as soon as the code gets executed till this point, the new desktop will be created and your application will be launched in that.

Now the question is when to close this desktop. In this case, we will switch to this desktop as soon as the TestApp gets closed. For that we can use the WaitForSingleObject. This will ensure that the calling application thread will wait till the process we launched finish execution and terminated. When you run the sample code, you will see that the TestLauncher application will launch TestApp and the TestApp will have End Test button.

Clicking on the button will close the TestApp and will trigger switching to original desktop.  When the new desktop is created and set, you will see that any sidebar or taskbar navigation are not present. This will also help preventing any other application to gain access to keystrokes or mouse interaction happening as they are in different desktop. The only way you can see running applications is by pressing Alt + Ctrl + Del combination and bringing up Task Manager. You can still disable this as well! I will leave this up to you if you want to try with the sample code provided.  Just Bing it!

Source code is here

By jay nanavati   Popularity  (3070 Views)