locked
Killing Another Users Process RRS feed

  • Question

  • I have a requirement as part of an application that must let certain users be able to kill the application for a selected user.  None of the users using the tool will be administrators on the machine, I cannot disable UAC, and the application for both the killer and killee are running on the same machine (Terminal Server).  

    I started out using the following code:

    
    

    System.Diagnostics.ProcessStartInfo processInfo = new System.Diagnostics.ProcessStartInfo("taskkill.exe", arguments); processInfo.Verb = "runas";

    var process = System.Diagnostics.Process.Start(processInfo); process.WaitForExit();


    
    

    This worked great for me because I am an admin on the machine.  When a non-admin tries to run this they get the UAC screen and are prompted for credentials.  This makes sense so I modified my code to this:

    System.Diagnostics.ProcessStartInfo processInfo = new System.Diagnostics.ProcessStartInfo("taskkill.exe", arguments); processInfo.Verb = "runas";

    processInfo.UseShellExecute = false; processInfo.CreateNoWindow = true;

    processInfo.UserName = "MYADMINUSERNAME";

    processInfo.Domain = "MYDOMAIN"; processInfo.Password = password;

    var process = System.Diagnostics.Process.Start(processInfo); process.WaitForExit();

    When I run this version of the code that passes credentials I get a 'Access is denied' error at:

    Stack Trace:

    Win32Exception:

       at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)

       at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)

    Is there anyway to let non-admin users run the taskkill.exe against a different users process without disabling UAC and without receiving the UAC prompt?

    Wednesday, October 9, 2013 5:52 PM

Answers

  • IIRC you're probably not going to get impersonation and Process.Start working like this.  I remember hearing about issues with it.  But you really don't need to go that far anyway.  Process.Start has an overload that allows you to specify the UN/PWD of a user.  Under the hood it uses CreateProcessAsUser which handles the impersonation for you.  You're still going to have the elevation issue though.

    But since you're using task scheduler I don't think you need to use the impersonation/overload anyway.  As I mentioned you can simply schedule the task to run as the specified user with the highest privilege and circumvent all of this.

    I should also mention that you don't really need to even start another process.  Once you're running as an admin then you can use Process.Kill to terminate a process.  So, given the requirement to be able to kill the processes of a user from within a scheduled task you should be able to do the following:

    • Configure the task to run under an admin account with highest privilege
    • Get the list of processes to terminate based upon whatever business requirement you have
    • Enumerate the processes and call Process.Kill

    If you need to kill a process within the context of an existing app that doesn't have admin rights then you're back to impersonation but without the need for Process.Kill.  You still have the elevated token issue though.

    Michael Taylor
    http://msmvps.com/blogs/p3net

    Thursday, October 10, 2013 2:42 PM

All replies

  • No, that is the purpose of UAC.  UAC prevents an application (irrelevant of who started it) from doing any admin level function.  The issue isn't whether you have admin rights or not but whether the account is elevated.  With UAC the elevation requires permission.  Once elevated you have full admin rights.  With UAC enabled admins don't start out elevated and AFAIK you cannot force the elevation without triggering a UAC prompt.  You can add a manifest to your app that says you must run elevated but starting your app will trigger the UAC prompt.

    So, without disabling UAC, you cannot get elevated privileges without a UAC prompt.  If you could then hackers would already be using it to work around UAC.  Having said that though I'm aware of at least one person who says you can work around it using the compatibility toolkit.  I've never confirmed it but it was based upon a KB article from MS.

    Michael Taylor
    http://msmvps.com/blogs/p3net

    Wednesday, October 9, 2013 6:06 PM
  • OK. Thanks for the thorough answer.  So I'm trying something different.  I'm trying to impersonate the admin user (which is working) before making the Process.Start call.  Now I am receiving a 'The system cannot find the file specified' error at:

    Stack Trace:

    Win32Exception:

       at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)

       at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)

    I'm not sure why impersonating a user would change whether or not 'taskkill.exe' is found or not.  Any ideas?


    Wednesday, October 9, 2013 7:02 PM
  • Impersonation is not sufficient.  As I mentioned earlier the issue isn't with whether the user is an admin or not but whether they are elevated.  Irrelevant of whether the user logs in directly or you impersonate they will get a standard token.  The UAC prompt is what changes that token from the standard to elevated token.  Until they are elevated admin functionality will fail.  AFAIK you cannot impersonate and get back the elevated token.  It simply isn't supported by Win32 but I haven't confirmed.  Additionally impersonation generally requires elevated privileges anyway so it is a catch 22.

    Since you're using scheduler to run this program you really don't need to worry about UAC.  When you set up the task you can tell TS to run the program at the highest privilege using any user account you want.  If you check that box for highest privilege then the account, if they are an admin, will run elevated.  I run some admin-only tool like this at startup.  Tasks are not your normal app.

    When you use impersonation you are loading the specified user's profile (by default) so if your app isn't in the system path the other user may or may not find the app.

    Wednesday, October 9, 2013 7:13 PM
  • Thanks again for the explanation.  I'm still hung-up on the 'The system cannot find the file specified'.  The app is just the windows 'taskkill' executable and it is present in both C:\windows\system32 and C:\windows\syswow64.  I even tried providing the full path to the Start() method.  If I run the code with no impersonation it works fine.  If I run it after I impersonate the domain administrator account I get the error.  The domain administrator account is an admin on my machine so I can't understand how it "cannot find the file specified".  

    Any ideas why impersonation would effect this call?

    Wednesday, October 9, 2013 7:58 PM
  • Could be a couple of different things.  Generally problems with impersonation revolve around the flags you're using.  Have you tried running something simple like notepad from the impersonated account?  Have you verified the impersonation is actually working for that user account?  Can you post the code you're using to impersonate and then the process start code?
    Wednesday, October 9, 2013 9:17 PM
  • public static class WindowsHelper
        {
            #region Member Variables
            
            private static Int16 LOGON32_LOGON_NETWORK = 3;
            private static Int16 LOGON32_LOGON_INTERACTIVE = 2;
            private static Int16 LOGON32_PROVIDER_DEFAULT = 0;
    
            #endregion
    
            #region External Calls
    
            [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
            private static extern bool CloseHandle(IntPtr handle);
    
            [DllImport("advapi32.dll", SetLastError = true)]
            private static extern int DuplicateToken(IntPtr ExistingTokenHandle, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
    
            [DllImport("advapi32.dll")]
            private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
    
            [DllImport("advapi32.dll")]
            private static extern int LogonUserA(String lpszUsername, String lszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
    
            [DllImport("advapi32.dll")]
            private static extern bool RevertToSelf();
    
            #endregion
    
            public static WindowsImpersonationContext Impersonate(string userName, string password, string domain)
            {
                WindowsImpersonationContext context = null;
                WindowsIdentity identity = null;
                IntPtr token = IntPtr.Zero;
                IntPtr tokenDuplicate = IntPtr.Zero;
    
                if (RevertToSelf())
                {
                    if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref token) != 0)
                    {
                        if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                        {
                            identity = new WindowsIdentity(tokenDuplicate);
                            context = identity.Impersonate();
                            if (context != null)
                            {
                                CloseHandle(token);
                                CloseHandle(tokenDuplicate);
                            }
                            else
                            {
                                throw new Exception("Unable to impersonate user using the identity provided.");
                            }
                        }
                        else
                        {
                            throw new Exception("Unable to duplicate token for user impersonation.");
                        }
                    }
                    else
                    {
                        throw new Exception("User impersonation failed using specified credentials.");
                    }
                }
    
                if (token != IntPtr.Zero)
                    try { CloseHandle(token); }
                    catch { }
                if (tokenDuplicate != IntPtr.Zero)
                    try { CloseHandle(tokenDuplicate); }
                    catch { }
    
                return context;
            }
    
            public static void UndoImpersonate(WindowsImpersonationContext context)
            {
                context.Undo();
            }
        }
    WindowsImpersonationContext context = WindowsHelper.Impersonate(EnvironmentSettings.AdminUser, EnvironmentSettings.AdminPassword, domain);
    
                try
                {
                    System.Diagnostics.ProcessStartInfo processInfo = new System.Diagnostics.ProcessStartInfo("taskkill.exe", arguments);
                    processInfo.Verb = "runas";
                    processInfo.UseShellExecute = true;
    
                    var process = System.Diagnostics.Process.Start(processInfo);
                    process.WaitForExit();
                }
                finally
                {
                    WindowsHelper.UndoImpersonate(context);
                }

    Here is the code with the impersonation code included.  I tried switching 'taskkill.exe' to 'notepad.exe' and received the same error.  The impersonation appears to be working.  A call to System.Security.Principal.WindowsIdentity.GetCurrent().Name after the impersonation is returning the username of the id I am impersonating.  Thanks again for your help.
    Thursday, October 10, 2013 12:43 PM
  • IIRC you're probably not going to get impersonation and Process.Start working like this.  I remember hearing about issues with it.  But you really don't need to go that far anyway.  Process.Start has an overload that allows you to specify the UN/PWD of a user.  Under the hood it uses CreateProcessAsUser which handles the impersonation for you.  You're still going to have the elevation issue though.

    But since you're using task scheduler I don't think you need to use the impersonation/overload anyway.  As I mentioned you can simply schedule the task to run as the specified user with the highest privilege and circumvent all of this.

    I should also mention that you don't really need to even start another process.  Once you're running as an admin then you can use Process.Kill to terminate a process.  So, given the requirement to be able to kill the processes of a user from within a scheduled task you should be able to do the following:

    • Configure the task to run under an admin account with highest privilege
    • Get the list of processes to terminate based upon whatever business requirement you have
    • Enumerate the processes and call Process.Kill

    If you need to kill a process within the context of an existing app that doesn't have admin rights then you're back to impersonation but without the need for Process.Kill.  You still have the elevated token issue though.

    Michael Taylor
    http://msmvps.com/blogs/p3net

    Thursday, October 10, 2013 2:42 PM