Environment - VS2008, Vista SP1.
I have written a process management service which can launch applications either in session 0 or the interactive console (usually 1). Please note this is NOT the normal mode of operation, it's for in-house debug purposes only. In the field, these processes will be safely hidden away in session 0. Security concerns do not apply.
Clearly people aren't reading this: security concerns do not apply. We have dozens of existing server apps (NOT services) written like this. We're not about to completely revamp these applications, we just need to be able to get at their inbuilt debug dialogs when running release versions in-house. I already know all about the canonical solution and pipes etc. If it was acceptable to add remote interfaces into all these apps, that's what we'd be doing.
I use the following code to do this:
ZeroMemory (&sui, sizeof(STARTUPINFO));
sui.cb = sizeof (STARTUPINFO);
sui.wShowWindow = pTask->GetWinStartState() ;
sui.dwFlags = STARTF_USESHOWWINDOW ;
ZeroMemory (&pi,sizeof(pi));
if (bInteractive)
{
HANDLE hToken = NULL;
DWORD dwSessionId = WTSGetActiveConsoleSessionId();
WTSQueryUserToken (dwSessionId, &hToken);
sui.lpDesktop = TEXT("winsta0\\default");
LPVOID pEnv = NULL;
DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
HMODULE hModu = LoadLibrary(TEXT("Userenv.dll"));
if (hModu )
{
if (CreateEnvironmentBlock (&pEnv, hToken, FALSE))
dwCreationFlag |= CREATE_UNICODE_ENVIRONMENT;
else
pEnv = NULL;
}
bCreatedOk = CreateProcessAsUser (hToken,
NULL,
(LPTSTR)(pTask->GetExeName()),
NULL,
NULL,
FALSE,
dwCreationFlag,
pEnv,
NULL,
&sui,
&pi);
}
else
{
bCreatedOk = CreateProcess (NULL, ... blah...);
}
This all works fine and I can run and monitor native processes both in the Vista service session and the console. Great. Cakes and ale for everyone.
So here's the problem. If I try to run a winforms (C#) app interactively like this, it appears to run, shows up in Process Explorer as running in session 1, but on the desktop... nada. No window appears at all. The process runs up and shuts down all fine, but no window ever appears. The exact same winform exe run from explorer also shows up in session 1, but this time appears on the desktop just fine.
Any ideas ?
Despite the evident hysteria there is nothing wrong with launching an application from a service into an interactive session provided it is done with the same privileges as the interactive user or lower. Since you are launching as the interactive user there can be no privilege escalation.
What you are doing does work. I suspect that the issue has something to do with your STARTUPINFO struct. You appear to be creating your sui on the stack but you don't show what you are doing with it. Are you initializing it to all 0s, if not you may be getting some garbage from the stack that is causing the window not to show or to show at some co-ordinates off the screen.
It looks as if you're trying to break Session Isolation. Like Mystere Man said, I don't think it's the right thing to do.
http://support.microsoft.com/?scid=kb%3Ben-us%3B165194&x=5&y=3 might be useful if you really want to do this. In the code sample over there, they seem to initialize more thing than you do.
EDIT If it's for debugging purposes, i-e VS 2008 step-in, just add a wait loop at the beginning, and launch VS with administrator rights. You can attach to your service process without encountering system isolation.
In a word, "don't".
Services typically run with reduced privileges and NOT as the current user. As such, in Vista+ they're not allowed to interact with the users desktop. On top of all that, services get a null Window Station.
You used to be able to check a box that said something like "Allow to interact with the desktop" but not anymore. It's bad practice.
Your best bet is to create a helper app that runs in the users context and communicates with the service via a named pipe, LRPC or a socket, then have your helper app launch the program for the user. This is the way most anti-virus now works.
Also, read this whitepaper from Microsoft on the subject. Services can't run in anything other than session 0.
NOTE: a little research seems to indicate that you need to duplicate the token using something like this:
DuplicateTokenEx(hTokenNew,MAXIMUM_ALLOWED,NULL,
SecurityIdentification,TokenPrimary,&hTokenDup);
Related
I need to close some external programs.
Does my app require admin privileges to use Windows API GetWindowThreadProcessId, OpenProcess and TerminateProcess or user mode is enought?
I've checked in XP and 7 and works fine, but I know newer versions are more restrictive.
I'm using the following code:
bool CloseApp(const char *WindowName)
{
HWND hWnd;
hWnd=FindWindow(nullptr,WindowName);
if(!hWnd)return(true); // Not running
DWORD pid;
GetWindowThreadProcessId(hWnd,&pid);
if(!pid)return(false);
HANDLE prc;
prc=OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE,FALSE,pid);
if(!prc)return(false);
TerminateProcess(prc,0);
DWORD rst=WaitForSingleObject(prc,10000); // Wait until completion
CloseHandle(prc);
if(FindWindow(nullptr,WindowName))return(false); // Check if still running
return(true);
}
Thanks.
It really depends on your application and the external process you want to kill. Windows has a pretty complicated system of authorizations for process interactions (everything revolves around Access Tokens).
Process
Since Windows Vista, process now have an integrity level which can be :
Untrusted. No logon SID, no access to the filesystem. Basically a pariah.
Low. Can only access %LocalLow% and a restricted set of API. Usually used to sandbox dangerous parts of an application (parsers, evaluators, etc.)
Medium. Your run-of-the-mill application/user
High. Superior privileges. Need UAC confirmation.
As you can imagine, lower integrity level applications can not interfere with higher integrity level ones. Since this mechanism is rarely used by developers (apart from MS products and web browsers) I assume your program run as Medium.
From integrity levels analysis, your program can probably only terminate process created by the same user and without admin privileges.
Services
Services are long-term process that have they own set of access mechanism. Services usually require admin levels (or even SYSTEM) to be controlled and terminated. Some can even be modified only by Network admins (e.g. in a AD) or only by Windows itself (e.g. for PPL services)
Anyway, the best way to check it is to call OpenProcess with your arguements and, if the calls fails, retrieve the last error set (using GetLastError() and check check for the ERROR_ACCESS_DENIED (0x5) value.
I'm writing a program to immediately track and kill when a user runs command prompt (and regedit if that's possible). This is to stop users from running commands I would rather they not have.
I've already written code that sees when a process is launched and checks its name using QueryFullProcessImageName. The issue is that if someone were to rename command prompt then I could no longer detect it via process name. The way I detect command prompt is currently "\cmd.exe" but clearly this is not very secure.
Posted below is what I have for the code. I removed all error checking for brevity. Please let me know if you need more clarity. Thanks!
TCHAR exeName[MAX_PATH];
DWORD exeNameSize = MAX_PATH;
//the pid comes into the function as a parameter
HANDLE handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid);
if (handle)
{
if (QueryFullProcessImageName(handle, 0, exeName, &exeNameSize))
{
tstring name = exeName;
/*
badProcs would contain the path identifiers such as
"\\cmd.exe" or "\\regedit.exe". This detection is
what I want to make better.
*/
for(int i=0; i < badProcs.size(); i++)
{
if(tstring::npos != name.find(badProcs.at(i)))
{
if(TerminateProcess(handle,0))
OutputDebugString(_T("Process should be dead\n\n"));
}
}
}
CloseHandle(handle);
}
Some additional information: The reason I'm writing this is to control what goes on in other desktops. I want to make it so that when a user launches a different desktop (via whatever proprietary program) I can control whether or not they have access to items which present the biggest security holes to the system. Given that I only want to control actions does on the other desktop, I do not want to change settings for fear of corrupting data outside of the target desktop. Is corruption not something to worry about?
I'm only interested in controlling a proprietary desktop, not mucking with what users do in their own space. Essentially the separate desktop is for corporate work, and I want to be able to limit what people can do with company information, etc.
Don't. Windows has internal means for that. Read up on the policy editor, and/or file access control.
If you're admin and the "user" is not, policy (or simple ACL) will do the job; if the "user" is also an admin, they'll be able to defeat your program fairly easily.
The best way to block the command prompt and registry editor is through the windows registry. These work even if you copy the executables to a different location.
Both the Registry Editor and Command Prompt cannot be run if the registry keys are set:
HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\System\DisableRegistryTools
or for the whole machine
HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\System\DisableRegistryTools
Setting this to 1 will disable regedit, and setting to 0 will enable it.
HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\System\DisableCMD
(the local machine varient works here as well).
Setting this to 1 will disable the command prompt and batch files, setting this to 2 will only disable the command line, and setting to 0 will enable it.
I have some code that used to work, but recently stopped. It's in an Adobe Reader Plugin, and the latest Reader version has a "Protected Mode" which causes my problem.
My plugin can load some of my normal dlls, which load in-process:
MyNormalLib::IMyClassPtr foo;
HRESULT hr = foo.CreateInstance(__uuidof(MyNormalLib::MyClass));
But when I try to get a com pointer to my service (running on the same machine):
MyOtherLib::IMyServicePtr bar;
HRESULT hr = bar.CreateInstance(__uuidof(MyOtherLib::MyService));
I get E_ACCESSDENIED for my HRESULT.
This used to work fine, until Adobe Reader X came along. Without Protected Mode, Adobe runs normally and everything works. With Protected Mode, Adobe spawns another Reader process, with some restrictions on it. Looking at this with Process Explorer, I can see that the Security Tab for the parent Reader process has pretty much everything set to Mandatory; but the child Reader process has most groups set to "Deny, Mandatory", some "Mandatory, Restricted", some are just Mandatory. If this matters, I can provide more details.
All processes (my service and both Reader) are run as the same user -- me. I have admin rights, if that matters.
What can cause an AccessDenied error when trying to reach my own service? What security hoops do I have to jump through to get this to work?
The restricted process does not have admin rights. That's pretty much the point of the exercise - Reader X drops as many rights as it can from its token so that if it is pwned your computer is not.
(That's basically how UAC works too, you have to go to the Kernel to get permission to re-enable your Administrator group once you have disabled it).
Basically you need to look at the privs that Reader X has, and make sure your component can be used with those permissions. Process Monitor is your friend - just filter for DENIED and the problems will pop right out at you!
I'm building a custom authentication subpackage for MSV1_0 for Windows 7. I've used the msvsubauth sample in from the Windows SDK and I have 2 questions regarding some problems I'm facing with that:
When I'm trying just to make sure that the routine get's invoked and set the Auth0 property in the registry to my package and add a simple code at the end of the Msv1_0SubAuthenticationRoutine that creates a file:
//
// Cleanup up before returning.
//
Cleanup:
hTestFile = CreateFile(
TEXT("C:\\lsa\\lsa.txt"),
GENERIC_READ|GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if(hTestFile != INVALID_HANDLE_VALUE) {
CloseHandle(hTestFile);
}
return Status;
} // Msv1_0SubAuthenticationRoutine
Apparently the package gets invoked because when I enter my password I get an error message from windows "the parameter is incorrect" which is a good sign. But why I'm getting that error? when the exactly same code is executed from a separate .exe file it runs perfectly and creates the test text file. I've checked the permissions and set "full control" for "everyone". Any ideas? the SDK doesn't exactly mention what kind of isolation LSA is creating for code within auth packages.
The second problem is testing the AP. Currently with every change I rebuild the library, copy it to a test VM and then to the System32 folder and reboot it. Is there an easier way to do that?
Thank in advance!
Debugging in Winlogon and LSASS makes for the most time consuming debugging.
To ease your debugging, you could write a proxy AP that exports the same functions. When loaded, you proxy_ap would
Copy the real AP from a known location to a temp locationand.
LoadLibrary that DLL, GetProcAddress of everything, and forward any calls it receives to that newly loaded DLL.
Watch for changes in the directory where the original AP was copied from
When a change occurs (and if your AP changed) FreeLibrary and goto step 2
But you need to keep a tight grip on what happens on your development target, because handling the dll switch while dealing with requests comming from many threads can become a worse nightmare that what you are trying to solve.
LogonUI.exe starts a new instance every time, but LSASS.exe is long lived.
+Have a look at CVSNT source code (http://cvsnt.sourcearchive.com/). They have a pretty nice AP that implements su. Run the sample in the local system account with psexec -s (from Microsoft/SysInternals pstools suite)
Perhaps your problem is Everyone only includes Authenticated users? This is just a guess.
I suggest you use Process Monitor to monitor for Access Denied messages or for your path. It is fantastic for debugging permission/path problems of all kinds.
http://technet.microsoft.com/en-us/sysinternals/bb896645.aspx
If you experience the issue at the "Unlock Workstation" or "change Password" screens, and it doesn't prevent you logging in, this should be easy to do - set it running, reproduce the problem, log back in and hey presto.
Otherwise you might have to resort to tricks like executing that code path only for certain user accounts, on the Nth try, etc.
I am writing a program that can be loaded by another service (under our control), or by the logged-on user. The program needs to know if the window station is interactive in order to display dialogs. I know GetProcessWindowStation function, but this one returns a handle. Is there a way to find out?
The interactive window station is always winsta0. So you need to get the window station name to determine it. Here is some pseudo code:
wchar_t buffer[256] = {0};
DWORD length = 0;
GetUserObjectInformation(GetProcessWindowStation(), UOI_NAME, buffer, 256, &length);
if (!lstrcmp(buffer, "winsta0")) {
// Interactive!
}
From http://msdn.microsoft.com/en-us/library/ms687096(VS.85).aspx:
The interactive window station, Winsta0, is the only window station that can display a user interface or receive user input
I suggest having the service pass command line parameters that let the program know it was launched by the service and not a user.
Please note that this only works on Windows XP (and then only sometimes) - on Windows Vista and beyond, services run in a separate session from interactive users, so you'll never be able to attach to the console on those OSs.
In addition on Windows XP, your application won't work if there are multiple users on the machine (Fast User Switching) because only the first user is logged onto session 0 (where services run).
You'd be far better off splitting your service into two pieces - the service which does the work and a small piece of code which runs as a task (using the Win32 task scheduler APIs) that runs the UI.