views:

354

answers:

4

So here's the scoop:

I wrote a tiny C# app a while back that displays the hostname, ip address, imaged date, thaw status (we use DeepFreeze), current domain, and the current date/time, to display on the welcome screen of our Windows 7 lab machines. This was to replace our previous information block, which was set statically at startup and actually embedded text into the background, with something a little more dynamic and functional. The app uses a Timer to update the ip address, deepfreeze status, and clock every second, and it checks to see if a user has logged in and kills itself when it detects such a condition.

If we just run it, via our startup script (set via group policy), it holds the script open and the machine never makes it to the login prompt. If we use something like the start or cmd commands to start it off under a separate shell/process, it runs until the startup script finishes, at which point Windows seems to clean up any and all child processes of the script. We're currently able to bypass that using psexec -s -d -i -x to fire it off, which lets it persist after the startup script is completed, but can be incredibly slow, adding anywhere between 5 seconds and over a minute to our startup time.

We have experimented with using another C# app to start the process, via the Process class, using WMI Calls (Win32_Process and Win32_ProcessStartup) with various startup flags, etc, but all end with the same result of the script finishing and the info block process getting killed. I tinkered with rewriting the app as a service, but services were never designed to interact with the desktop, let alone the login window, and getting things operating in the right context never really seemed to work out.

So for the question: Does anybody have a good way to accomplish this? Launch a task so that it would be independent of the startup script and run on top of the welcome screen?

+1  A: 

I think you can do it, but it's pretty involved. Interactive apps aren't normally allowed to run on the welcome screen. At a high level, you'll need to:

  • Create a windows service that starts automatically
  • Use the windows service to create another process on the current session and desktop (using the Win32 methods WTSGetActiveConsoleSessionId and OpenInputDesktop)

I wrote an app that can interact somewhat with the login screen, but it doesn't show any UI. It probably can be done, but it may be even more involved.

Note: I found that I was unable to get results from OpenInputDesktop from my Windows service. I had to instead make the call in the other process and notify the service to restart the process on the correct desktop.

I hope that can at least get you started. Good luck!

dcstraw
That PSexec can do it tells me its possible, its just a matter of mimicking how it's launching it I guess. I will look into what you suggested and see where it gets me.
peelman
+2  A: 

This is one of those "You really need a good reason to do this" questions. Microsoft tries very hard to block applications running at the startup screen - every bit of code in Windows which interacts with the logon screen is very carefully code reviewed because the security consequences of a bug in code running at the logon screen are dire - if you screw up even slightly, you'll allow malware to get onto the computer.

Why do you want to run your program at the logon screen? Maybe there's a documented way of doing it that's not as risky.

Larry Osterman
I understand Microsoft's paranoia, but this is one of those things that makes me hate Windows that much more.We need info on the screen for a variety of reasons, most of which are our own. The program in question takes no user input, merely displays information. The odds of a 14k C# app being a security risk is not an issue to us, particularly when we have 50,000 potential users per machine.
peelman
Can the information you want to display fit in a message box? Windows has a mechanism to display a text string in a message box at logon time that might do what you want.
Larry Osterman
Going back to my original post, this is machine data for support and operations staff, so they don't have to log into a machine to look at it. It is important that it be at-a-glance, always available info. OSX provides similar info in the login window, which saves us from having to do something similarly complicated there.
peelman
What kind of information do they need that can't be retrieved remotely via WMI? Windows has a staggering amount of management information which can be read using the WMI APIs that's intended for support and operations staff. Is there information they need that's only available at the physical console?
Larry Osterman
Ya know, if you're not going to help me solve the problem i asked about, i just as soon not debate about creating new problems.
peelman
From where I sit, I *am* trying to help you solve the problem. There's no reliable way of doing what you want without introducing potential security threats to the system, so I'm trying to come up with an alternative mechanism that lets you retrieve the information you need without hacking around system security features.About once every 3 months, someone from inside MSFT asks a similar question on our internal "windows tricks" alias and the answer is always the same as the one I'm giving - find another way of doing what you want because it's safer for *all* our customers.
Larry Osterman
And before you respond "If you keep getting these kinds of requests, doesn't it show you that you need to make this secure?", there's no way of fixing this issue - code running at the logon desktop is vulnerable to shatter attacks (http://en.wikipedia.org/wiki/Shatter_attack), unless you carefully make sure that your UI logic is safe from them (which means long, involved code reviews of every bit of UI code, including the WPF codebase you're using), there's no way of ensuring that your application is totally safe. It's invariably better to find another way.
Larry Osterman
Well, again, i appreciate the concern, but we are already doing it, via psexec, so wouldn't it be fairly obvious that we are both aware and could care less about e security issues? And what part of "50,000 users" was unclear? These are not NSA workstations, they are computer lab machines on a very large college campus. Your message box trick I am aware of, that would require every student logging in to confirm the box--which is unacceptable. Our ops and support staff are walking into rooms with 20, 30, or even 100 machines, having this info on the login screen is important to us.
peelman
Im very aware of the capabilities of wmi, and its remote power, but that does us no good when we are trying to verify or correct problems with a physical station. If MS was capable of some forethought, they could have built SOME extensibility into authui.dll, either by allowing it to read a configuration XML file, similar to the ones internal to it, or worse, via registry keys. Neither solution would be ideal for our deep freeze state, ip address, or clock, since all of that info needs to be refreshed regularly.
peelman
Here's the risk you run: You get your UI running on the desktop and some clever student figures out how to exploit your UI. They now 0wn your desktop. They repeat for every machine in the lab, they now 0wn your lab. Now some domain administrator logs into the machine in the lab. Now your clever student 0wns your domain. And once the clever student 0wns your domain, it's game over.There are solutions to this: For instance, I know universities (and companies) that reimage every lab computer every night. But those remedies tend to be rather draconian.
Larry Osterman
Simply having Windows machines on campus is a risk, as you define it. There are much larger holes in the system they can exploit, Especially if they apparently can escalate from a process running as a local user to something with domain privs.My point is, these are our machines, that is our call to make. We make security trade offs every day to keep our machines functional for both us and our end users.
peelman
And just to paint you an even bleaker picture, the users sit down at our machines, crack the case, reset the BIOS, which clears the password, they can then boot from whatever they want a modify our drives how they see fit, and let the machines boot, bypassing all of our "security." They install a keylogger and start grabbing passwords.Our nightmare scenarios do not revolve around a bored CS student trying to exploit some stupid little transparent window on the login screen made up of nothing but labels.
peelman
When the users have physical access to the machines your security is already compromised. This app does not present a *significant* network threat to us, and that's all we really care about.If what you say is in fact true, then the fact that there is no way to run a process like this, in a safe manner, really speaks volumes about the "improvements" to Windows' security. Instead of making it better, lets just restrict what people can do.Here's an idea to make the machines even more secure: We'll just unplug them from the network and take away the keyboards and mice.
peelman
+4  A: 

This can be done through a lot of Win32 API calls. I have managed to get a program with a GUI onto the Winlogon desktop (before anyone asks, it's not an interactive GUI). Basically you need to run a loader process as SYSTEM, which will then spawn the new process. Since you most likely want this process to run on start up, you can either use the task scheduler to run the loader as SYSTEM or you can use a service to do the same thing. I'm currently using a service, but I tried using the task scheduler and it did work just fine.

Short summary:

  1. Grab the Winlogon.exe process (as a Process)
  2. Grab the token of winlogon using OpenProcessToken using the .handle of the Process
  3. Create a new token and duplicate the winlogon token to it
  4. Elevate the privileges of the token
  5. Create the process using CreateProcessAsUser, making sure to set lpDesktop to "Winsta0\Winlogon" and using the token you created.

Code example:

        // 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, process, process, ref tokenAttributes, ref threadAttributes,
            true, (uint)CreateProcessFlags.CREATE_NEW_CONSOLE | (uint)CreateProcessFlags.INHERIT_CALLER_PRIORITY, IntPtr.Zero,
            logInfoDir, 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");
        }
Jon
A: 

Anyone have this working and can post the project somewhere? I can't get it working properly. Thanks

Ray