0

I have 3 applications running:

Application A: Local server running as a background service under Local System with permission to interact with the desktop ( Atleast that is what Advanced Installer says )

Application B: A desktop capture software

Application C: A tray icon running as the logged on user.

What i am trying to achieve is to be able to capture the desktop UAC prompts and logon screen, so here is how i am trying to achieve it:

1. Application C calls WTSGetActiveConsoleSessionId() ( which return 2), Grab the desktop pointer:

OpenInputDesktop(0, false, (uint)(ACCESS_MASK.DESKTOP_CREATEMENU | ACCESS_MASK.DESKTOP_CREATEWINDOW | ACCESS_MASK.DESKTOP_ENUMERATE | ACCESS_MASK.DESKTOP_HOOKCONTROL | ACCESS_MASK.DESKTOP_WRITEOBJECTS | ACCESS_MASK.DESKTOP_READOBJECTS | ACCESS_MASK.DESKTOP_SWITCHDESKTOP))

and sends both to the application A ( local server )

2. The server runs the following code:

static void CreateApplicationBProcess()
{
            IntPtr hToken = IntPtr.Zero;
            IntPtr P = IntPtr.Zero;            
            /*if (!OpenProcessToken(OpenProcess(Process.GetProcessesByName("winlogon").First(), ProcessAccessFlags.All), 0x2000000, out hToken))
            {
                throw new Exception("OpenProcessToken error #" + Marshal.GetLastWin32Error());
            }*/

            if (!WTSQueryUserToken(WTSGetActiveConsoleSessionId(), out hToken))
            {
                throw new Exception("WTSQueryUserToken error #" + Marshal.GetLastWin32Error());
            }

            IntPtr duplicatedTokenHandle = IntPtr.Zero;
            if(!DuplicateTokenEx(hToken, 0, IntPtr.Zero, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref duplicatedTokenHandle))
            {
                throw new Exception("DuplicateTokenEx error #" + Marshal.GetLastWin32Error());
            }

            uint sessionId = SessionId;
            if(!SetTokenInformation(duplicatedTokenHandle, TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, (uint)IntPtr.Size))
            {
                throw new Exception("SetTokenInformation error #" + Marshal.GetLastWin32Error());
            }

            if (CreateEnvironmentBlock(out P, duplicatedTokenHandle, false))
            {
                Win32.PROCESS_INFORMATION processInfo = new Win32.PROCESS_INFORMATION();
                Win32.STARTUPINFO startInfo = new Win32.STARTUPINFO();
                bool bResult = false;
                uint uiResultWait = Win32.WAIT_FAILED;
                try
                {
                    // Create process
                    startInfo.cb = Marshal.SizeOf(startInfo);
                    startInfo.lpDesktop = "winsta0\\default";
                    startInfo.dwFlags = 0x00000001;
                    startInfo.wShowWindow = (short)SW.SW_HIDE;

                    SetCurrentDirectory(GetDirectory());
                    Environment.CurrentDirectory = GetDirectory();

                    bResult = Win32.CreateProcessAsUser(duplicatedTokenHandle, Path.Combine(GetDirectory(), "ApplicationB.exe"), string.Format("{0}", (uint)DesktopPtr), IntPtr.Zero, IntPtr.Zero, false,
                        CREATE_UNICODE_ENVIRONMENT | CREATE_NO_WINDOW, P, GetDirectory(), ref startInfo, out processInfo);
                    if (!bResult)
                    {
                        throw new Exception("CreateProcessAsUser error #" + Marshal.GetLastWin32Error());
                    }
                    else
                    {
                        //IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)processInfo.dwThreadId);
                        ResumeThread(processInfo.hThread);
                    }
                    // Wait for process to end
                    uiResultWait = Win32.WaitForSingleObject(processInfo.hProcess, Win32.INFINITE);
                    if (uiResultWait == Win32.WAIT_FAILED)
                    {
                        throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error());
                    }

                    GetExitCodeProcess(processInfo.hProcess, out uint ExitCode);
                    Console.WriteLine("Exit code: " + ExitCode);
                }
                finally
                {
                    // Close all handles
                    Win32.CloseHandle(hToken);
                    Win32.CloseHandle(processInfo.hProcess);
                    Win32.CloseHandle(processInfo.hThread);
                    DestroyEnvironmentBlock(P);
                }
            }
            else
            {
                throw new Exception("CreateEnvironmentBlock error #" + Marshal.GetLastWin32Error());
            }
        }


        private static string GetDirectory()
        {
            return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        }

3. Now the CreateProcessAsUser returns a success but the process is exiting with the error code 3228369022 (C06D007E).

I monitored with ProcMon and i dont have any error with Load Image, however not all of my dlls are loaded.

  • this is `VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND)` - you need debug process for view which concrete. usual this when resolving delay import – RbMm Aug 26 '19 at 18:36
  • `OpenProcessToken` for what you need winlogon token ?! error. you need use token returned from `WTSQueryUserToken` but not from `OpenProcessToken` in call `CreateProcessAsUser` – RbMm Aug 26 '19 at 18:40
  • `OpenThread` for what ?!? when you already have handle for this thread. what for this thread for what ? – RbMm Aug 26 '19 at 18:42
  • @RbMm when i had the debugger flag set, the process was starting as `Suspended` so i had to resume it. What i am trying to achieve is to run the applicaiton b has the System account ( this is why the winlogon token ) and run it in the user session, i got this from: https://stackoverflow.com/questions/53346426/c-taking-a-screenshot-of-the-windows-logon-screen-uac-prompts-without-disabli/53354129#53354129 – Sylvain Martens Aug 26 '19 at 18:53
  • you mistake - when debugger flag set ( `DEBUG[_ONLY_THIS]_PROCESS` you mean ?) process not started as suspended. and you not need resume thread. thread started suspended - when and only when you use `CREATE_SUSPENDED`. then you not need open thread because you already have it handle. you not need resume it. if you need run with system account in user session - duplicate **self** token and set session id in it – RbMm Aug 26 '19 at 19:02
  • Im not sure what you mean by `self`, service is running as `local system` not `system`, and how do you Set session id ?, can you provide a sample so i can understand better. – Sylvain Martens Aug 26 '19 at 19:09
  • i mean open your process token, duplicate and set sessionid in it. local system this is the **same** as system – RbMm Aug 26 '19 at 19:15
  • *Set session id ?* - [`SetTokenInformation`](https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-settokeninformation) with [`TokenSessionId`](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-token_information_class) – RbMm Aug 26 '19 at 19:18
  • @RbMm getting exit code `3228369022` if i look the even viewer this is what i get: Faulting application name: ApplicationB.exe, version: 1.0.0.0, time stamp: 0xb69f5596 Faulting module name: KERNELBASE.dll, version: 10.0.17763.652, time stamp: 0x57e2061a Exception code: 0xc06d007e Fault offset: 0x0011fd82 Faulting process id: 0x628 Faulting application start time: 0x01d55c436c7abdae Faulting application path: C:\Program Files (x86)\...PATH...exe Faulting module path: C:\WINDOWS\System32\KERNELBASE.dll Report Id: e6f0d5ad-e98c-4864-882d-7d3101f9fda4 https://i.imgur.com/LybhVv4.png – Sylvain Martens Aug 26 '19 at 19:24
  • @RbMm can you add me on skype to help debugging this before i go crazy: wisahesa – Sylvain Martens Aug 26 '19 at 19:28
  • `0xc06d007e` - this is `VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND)` used primary in delay load import dll resolving – RbMm Aug 26 '19 at 19:30
  • yea all my processes and dlls are compiled in 32 bits, and everything is present in the working directory. so i dont know why it would fail loading, and i looked up with Process Monitor and it doesnt says it fails to load any dll's. Do you know a way to debug delay load dll resolving ? – Sylvain Martens Aug 26 '19 at 19:32
  • help debug i of course can, but for this need or access to comp where this problem or have reproducible example on self comp :) *Do you know a way to debug delay load dll resolving ?* - know of course, but need access binary code – RbMm Aug 26 '19 at 19:32
  • and anyway - i be suggest first improve your code for current sample (remove open winlogon, thread, wait on thread, etc) – RbMm Aug 26 '19 at 19:34
  • @RbMm add me on skype, il send you teamviewer access. – Sylvain Martens Aug 26 '19 at 19:36

1 Answers1

2

source of error was

startInfo.lpDesktop = "winsta0\\default";

line, because CreateProcessAsUserW used (not visible from question code)

so must be

startInfo.lpDesktop = L"winsta0\\default";

also code contains several another errors:

we not need open winlogon.exe (or another hardcoded exe name token) instead we can open self process token and use it here, if we want system token use.

not need DuplicateTokenEx +set TokenSessionId for token returned by WTSQueryUserToken - because the WTSGetActiveConsoleSessionId() already was in this token (we get it by this session id)

ResumeThread senseless call here - because thread created suspended when and only when CREATE_SUSPENDED flag used in CreateProcess

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Fixed for c# you need to change the DllImport of the function CreateProcessAsUser and the StructLayout of STARTUPINFO to use both CharSet = CharSet.Unicode – Sylvain Martens Aug 27 '19 at 13:15