3

I need to write a small service that runs an application (with gui, e.g. calc.exe) on the logon-screen.

I already found this question (and answer): Running a process at the Windows 7 Welcome Screen

please read the code-comments if you don't understand how this works:

        // grab the winlogon process
        Process winLogon = null;
        foreach (Process p in Process.GetProcesses())
        {
            if (p.ProcessName.Contains("winlogon"))
            {
                winLogon = p;
                break;
            }
        }
        // grab the winlogon's token
        IntPtr userToken = IntPtr.Zero;
        if (!OpenProcessToken(winLogon.Handle, TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE, out userToken))
        {
            log("ERROR: OpenProcessToken returned false - " + Marshal.GetLastWin32Error());
        }

        // create a new token
        IntPtr newToken = IntPtr.Zero;
        SECURITY_ATTRIBUTES tokenAttributes = new SECURITY_ATTRIBUTES();
        tokenAttributes.nLength = Marshal.SizeOf(tokenAttributes);
        SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
        threadAttributes.nLength = Marshal.SizeOf(threadAttributes);
        // duplicate the winlogon token to the new token
        if (!DuplicateTokenEx(userToken, 0x10000000, ref tokenAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
            TOKEN_TYPE.TokenImpersonation, out newToken))
        {
            log("ERROR: DuplicateTokenEx returned false - " + Marshal.GetLastWin32Error());
        }
        TOKEN_PRIVILEGES tokPrivs = new TOKEN_PRIVILEGES();
        tokPrivs.PrivilegeCount = 1;
        LUID seDebugNameValue = new LUID();
        if (!LookupPrivilegeValue(null, SE_DEBUG_NAME, out seDebugNameValue))
        {
            log("ERROR: LookupPrivilegeValue returned false - " + Marshal.GetLastWin32Error());
        }
        tokPrivs.Privileges = new LUID_AND_ATTRIBUTES[1];
        tokPrivs.Privileges[0].Luid = seDebugNameValue;
        tokPrivs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        // escalate the new token's privileges
        if (!AdjustTokenPrivileges(newToken, false, ref tokPrivs, 0, IntPtr.Zero, IntPtr.Zero))
        {
            log("ERROR: AdjustTokenPrivileges returned false - " + Marshal.GetLastWin32Error());
        }
        PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
        STARTUPINFO si = new STARTUPINFO();
        si.cb = Marshal.SizeOf(si);
        si.lpDesktop = "Winsta0\\Winlogon";
        // start the process using the new token
        if (!CreateProcessAsUser(newToken, "calc.exe", null, ref tokenAttributes, ref threadAttributes,
            true, (uint)CreateProcessFlags.CREATE_NEW_CONSOLE | (uint)CreateProcessFlags.INHERIT_CALLER_PRIORITY, IntPtr.Zero,
            "C:\\Windows\\System32", ref si, out pi))
        {
            log("ERROR: CreateProcessAsUser returned false - " + Marshal.GetLastWin32Error());
        }

        Process _p = Process.GetProcessById(pi.dwProcessId);
        if (_p != null)
        {
            log("Process " + _p.Id + " Name " + _p.ProcessName);
        }
        else
        {
            log("Process not found");
        }

It works with Windows 7. With XP I get error 1349 ERROR_BAD_TOKEN_TYPE while calling CreateProcessAsUser (MSDN: The type of the token is inappropriate for its attempted use.).

How to realize this in Windows XP? It does not have to be the code from above but it should work as a service (with system-account?).

Thanks for your support Fluxer

Community
  • 1
  • 1
MariusK
  • 537
  • 5
  • 20

1 Answers1

3

This definitely has to do with privilege issues (Windows Vista and 7 have notable changes in security). Rather than trying to get token of winlogon.exe and impersonating it, try getting user token via WTSQueryUserToken like this:

WTSQueryUserToken (WTSGetActiveConsoleSessionId(), out userToken);

Replace the OpenProcessToken line which is used to get the token with the above statement.

Your new code should be like this:

//        if (!OpenProcessToken(winLogon.Handle, TOKEN_QUERY | TOKEN_IMPERSONATE | //TOKEN_DUPLICATE, out userToken))
//        {
//            log("ERROR: OpenProcessToken returned false - " + //Marshal.GetLastWin32Error());
//        }

WTSQueryUserToken (WTSGetActiveConsoleSessionId(), out userToken);

Do your dll import like this:

[DllImport("Kernel32.dll", SetLastError = true)]
[return:MarshalAs(UnmanagedType.U4)]
public static extern int WTSGetActiveConsoleSessionId ( );

You need only replace the part I commented out with this code.

Chibueze Opata
  • 9,856
  • 7
  • 42
  • 65
  • Thanks for your answer! what should I do with token `&newToken`? just using it for `CreateNewProcessAsUser`? – MariusK Mar 26 '12 at 11:55
  • thanks for your editing. I replaced the code but still getting Error code 1349 on calling `CreateProcessAsUser`.. – MariusK Mar 26 '12 at 12:38
  • if I do not duplicate token and call `CreateProcessAsUser` with `userToken` instead of `newToken`, the process starts but is not visible. neither in locked nor in unlocked mode of win xp. – MariusK Mar 26 '12 at 12:52
  • Sorry, had problems getting across because of internet connectivity issues. Can you update the question with your modified code? If this doesn't work, then I guess you should try the scheduling option instead. – Chibueze Opata Mar 26 '12 at 14:33
  • Hello, I tried it with `CreateProcess` and the same startupinfos as above and it works! thank you. – MariusK Mar 28 '12 at 06:41