How to prevent an app from being killed in task manager?

newman picture newman · Jun 6, 2013 · Viewed 8.8k times · Source

I'm working on a parental control app (written in WPF) and would like to disallow anybody (including administrator) to kill my process. A while back, I found the following code online and it almost works perfectly, except that it doesn't work sometimes.

static void SetAcl()
{
    var sd = new RawSecurityDescriptor(ControlFlags.None, new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), null, null, new RawAcl(2, 0));
    sd.SetFlags(ControlFlags.DiscretionaryAclPresent | ControlFlags.DiscretionaryAclDefaulted);
    var rawSd = new byte[sd.BinaryLength];

    sd.GetBinaryForm(rawSd, 0);
    if (!Win32.SetKernelObjectSecurity(Process.GetCurrentProcess().Handle, SecurityInfos.DiscretionaryAcl, rawSd))
        throw new Win32Exception();
}

In Win7, if the app is started by the logged in user, even the admin cannot kill the process (access denied). However, if you switch to another user account (admin or standard user), then check "Show processes for all users", then you kill the process without a problem. Can anybody give me a hint why and how to fix it?

EDIT:
I understand some people are upset by this question, but here is my dilemma. This is a parental control I wrote primarily for my own use. The main feature is that I want to monitor and limit my kids' on games (not simply turn off all games). I could assign kids a standard user account and they cannot kill the process. However, some games (e.g. Mabinogi) require admin right to be playable. So, I had to type in my admin password each time, which is annoying.

By the way, I'm not sure if it's against Stackoverflow's policy, here is my app if you'd like to check it out: https://sites.google.com/site/goppieinc/pc-screen-watcher.

EDIT:
My main point of this post is to ask if somebody could give me a hint why the posted code doesn't always work - e.g. in case you show processes for all users.

Answer

user7000146 picture user7000146 · Feb 5, 2017

Some of the comments are right, you're playing a game that might very well be doomed to have no end. However, from what I can tell, setting your process as a critical kernel process appears to give you the clear victory. Any attempt to kill the process will simply BSOD your computer. Code is:

/*
Copyright © 2017 Jesse Nicholson  
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/


using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace MyRedactedNamespace
{
    /// <summary>
    /// Class responsible for exposing undocumented functionality making the host process unkillable.
    /// </summary>
    public static class ProcessProtection
    {
        [DllImport("ntdll.dll", SetLastError = true)]
        private static extern void RtlSetProcessIsCritical(UInt32 v1, UInt32 v2, UInt32 v3);

        /// <summary>
        /// Flag for maintaining the state of protection.
        /// </summary>
        private static volatile bool s_isProtected = false;

        /// <summary>
        /// For synchronizing our current state.
        /// </summary>
        private static ReaderWriterLockSlim s_isProtectedLock = new ReaderWriterLockSlim();

        /// <summary>
        /// Gets whether or not the host process is currently protected.
        /// </summary>
        public static bool IsProtected
        {
            get
            {
                try
                {
                    s_isProtectedLock.EnterReadLock();

                    return s_isProtected;
                }
                finally
                {
                    s_isProtectedLock.ExitReadLock();
                }
            }
        }

        /// <summary>
        /// If not alreay protected, will make the host process a system-critical process so it
        /// cannot be terminated without causing a shutdown of the entire system.
        /// </summary>
        public static void Protect()
        {
            try
            {
                s_isProtectedLock.EnterWriteLock();

                if(!s_isProtected)
                {
                    System.Diagnostics.Process.EnterDebugMode();
                    RtlSetProcessIsCritical(1, 0, 0);
                    s_isProtected = true;
                }
            }
            finally
            {
                s_isProtectedLock.ExitWriteLock();
            }
        }

        /// <summary>
        /// If already protected, will remove protection from the host process, so that it will no
        /// longer be a system-critical process and thus will be able to shut down safely.
        /// </summary>
        public static void Unprotect()
        {
            try
            {
                s_isProtectedLock.EnterWriteLock();

                if(s_isProtected)
                {
                    RtlSetProcessIsCritical(0, 0, 0);
                    s_isProtected = false;
                }
            }
            finally
            {
                s_isProtectedLock.ExitWriteLock();
            }
        }
    }
}

The idea here is that you call the Protect() method ASAP, and then call Unprotect() when you're doing a voluntary shutdown of the app.

For a WPF app, you're going to want to hook the SessionEnding event, and this is where you'll call the Unprotect() method, in case someone logs off or shuts down the computer. This absolutely must be the SessionEnding event, and not the SystemEvents.SessionEnded event. Very often by the time the SystemEvents.SessionEnded event is called, your application can be force terminated if you're taking too long to release the protection, leading to a BSOD every time you restart or log off. If you use the SessionEnding event, you avoid this problem. Another interesting fact about that event is that you're able to, to some degree, contest the logoff or shutdown. Also you'll obviously want to call Unprotect() inside the Application.Exit event handler.

Ensure that your application is stable before deploying this mechanism, because a crash would also BSOD your computer if the process is protected.

As a note for all of the people attacking you for taking these measures, please ignore them. It doesn't matter if someone could potentially use this code to do something malicious, that's a poor excuse to stop perfectly legitimate research for a perfectly legitimate cause. In my case I have developed this as part of my own application, because adults (administrators) don't want to be able to stop my process by killing it. They explicitly desire this functionality, because it prevents themselves from being able to bypass what the software was designed to do.