11

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.

Hille
  • 2,123
  • 22
  • 39
newman
  • 6,841
  • 21
  • 79
  • 126
  • 18
    You know what I'd like? An app that lets me kill any process even if it is unkillable. And then I suppose you'd like to make your process not killable by that app. And then I'd like a killer for that app. The game you're playing is called "walls and ladders"; I can always build a ladder as tall as your wall. Stop playing this game. The administrator owns the computer, not you, so they get to decide what runs and what does not. – Eric Lippert Jun 06 '13 at 17:39
  • 3
    See http://blogs.msdn.com/b/oldnewthing/archive/2012/01/17/10257351.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2011/03/10/10138969.aspx for other examples. – Eric Lippert Jun 06 '13 at 17:40
  • 8
    Moreover: if you have admin permission then you can always kill an app by attaching to it with the debugger and killing it there. Of course, if a child is attaching to your parental control software with the debugger in order to subvert it, I submit to you that the child already has thoroughly beaten the parent at this game. – Eric Lippert Jun 06 '13 at 17:42
  • Virus writers and anti-virus writers have been in this arms race a long time. And they're probably the main audience interested in the topic. Not sure whether stackoverflow is the right forum. – hatchet - done with SOverflow Jun 06 '13 at 17:42
  • Guys, he probably just wants to stop some high schoolers from watching porn. If the kids can beat the system, their place isn't in high school anyway - they should be working in IT already. – Geeky Guy Jun 06 '13 at 17:44
  • If you make it taskmanager proof, what about easy downloads like pskill? – hatchet - done with SOverflow Jun 06 '13 at 17:48
  • 4
    @Renan Not necessarily. It only takes one person to learn how to subvert the mechanism, then they can either write a program or create step by step instructions that anyone can just follow. Once the concepts make it onto the internet (as they inevitably do) learning these simple steps for how to bypass software such as this becomes simple googling. – Servy Jun 06 '13 at 17:59
  • If it's just for your own computer, you could remove the taskmanager.exe file to a place only you have access. – hatchet - done with SOverflow Jun 06 '13 at 18:02
  • @hatchet: well, it's primarily for my own use now, but I'm hoping it will be useful to general public. It's free. – newman Jun 06 '13 at 18:11
  • 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. – newman Jun 06 '13 at 18:19
  • Are you sure the games require admin rights. My kids play many old and new games with a standard account. Some games require write access to its program folder but thats the only "admin" problem i have seen for a long time. A long time ago I used CPAU to start some games in admin mode without password. – adrianm Jun 06 '13 at 19:17
  • Well, I just checked...PlanetSide 2 and Mabinogi do prompt for admin password for standard users. see this post "Is there a way to play Mabinogi without the starter?" at http://answers.yahoo.com/question/index?qid=20090610110116AA6ojPo – newman Jun 06 '13 at 19:34
  • 2
    @miliu well if it's for your own use, How about you tell your kids if they kill your application by doing whatever, they don't get to play for the next 3 weeks :) and also cannot any longer play games which require admin control and see if they even dare to do it after tht. Get your app to record time alive and you can check that up and make sure it was kept alive once in a while during their sessions – Viv Jun 07 '13 at 09:04
  • 1
    @Viv yes, that's the practice I use now. However, it doesn't work well. For example, while I'm absent, he could shutdown my app and play some games and restart it. He could claim he didn't play at all. Furthermore, as a software engineer, I'm not satisfied with this approach. Besides, it's not strictly for my own use, and I'm hoping to be useful for general public. – newman Jun 07 '13 at 14:01

4 Answers4

12

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.

  • Thank you for this! I used this on my Windows Service App and it worked like a charm on the computer I was developing it on. However, once I installed the service on another computer, the process was no longer protected or marked as critical. Has anyone encountered this issue? Any advice? – INeedHelpFrequently Jan 01 '21 at 05:53
1

Make it so that the WPF side is just a client. The "server" in this case must be a Windows Service. Then set the Service to start automatically (this last part requires admin privileges). Bonus if it runs as a network admin.

If the service's process is killed, Windows starts it again immediately. And then no matter what users try, they can't really stop your program's logic unless they have admin powers and stop the service themselves. Use the WPF GUI just for configuration.

Geeky Guy
  • 9,229
  • 4
  • 42
  • 62
  • Thank you very much for the tip. I was thinking about this approach too. However, as I mentioned, giving kids a standard account is a problem. I wish Windows has more granular control over the account for admin. – newman Jun 06 '13 at 18:16
  • Give each kid a [Steam](http://store.steampowered.com) account. That way you know what they're playing just by checking their profile (it has a social network side, and you can't hide what you've been playing, for how long etc.). You'll have a lot more control over what they play, since they'll only play what you buy them (note that there is no signature fee). – Geeky Guy Jun 06 '13 at 18:30
  • I don't know what Steam is, but I know my kids do have it on their computer. The problem I try to solve with my program is that I want to grant my kids certain time of games (e.g. max 2 hrs per day on weekends, and no game during weekdays). I don't care which game they play as long as time allows. The games they play change from time to time. Besides, I can monitor all the activities on their computers as long as my screen watcher is on... – newman Jun 06 '13 at 18:47
  • Steam will help you with that as well. It acts as a launcher to the games, so killing Steam's process kills whatever they are playing too. – Geeky Guy Jun 06 '13 at 18:48
  • Well, I just looked it up and I don't see any parental control function in Steam. Actually, many people are asking for that. Besides, I saw kids playing games without running steam. – newman Jun 06 '13 at 19:02
  • You don't need Steam to play games. You need Steam to play the games you bought (or were given) through Steam. As for the parental function, yeah it doesn't have one. I just mentioned it because you want to kill processes, and the Steam Client process is a parent process of any game's that's started through it. – Geeky Guy Jun 06 '13 at 19:05
0

The system account is higher (at least in case of OS) than administrator. The system account and admin account have the same file privileges, but they have different functions. The system account is used by the operating system and by services that run under Windows. There are many services and processes within Windows that need the capability to log on internally (for example during a Windows installation). The system account was designed for that purpose; it is an internal account, does not show up in User Manager, cannot be added to any groups, and cannot have user rights assigned to it.

So the challenge is how to elevate your application privilege to system account while on course of installation. I am not sure how to elevate your process. But worth reading following post ref 1, ref 2. In addition, even if we assume you managed to get it to system account, you might still face more challenge when it comes to managing your own application even being an exponentially super user.

S.N
  • 4,910
  • 5
  • 31
  • 51
0

You can not stop an Administrator from killing your process or stopping your service with this code but may be some tweeks may end up with something can.

//Obtaining the process DACL
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool GetKernelObjectSecurity(IntPtr Handle, int securityInformation, [Out] byte[] pSecurityDescriptor, uint nLength, out uint lpnLengthNeeded);
public static RawSecurityDescriptor GetProcessSecurityDescriptor(IntPtr processHandle)
{
    const int DACL_SECURITY_INFORMATION = 0x00000004;
    byte[] psd = new byte[0];
    uint bufSizeNeeded;
    // Call with 0 size to obtain the actual size needed in bufSizeNeeded
    GetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, psd, 0, out bufSizeNeeded);
    if (bufSizeNeeded < 0 || bufSizeNeeded > short.MaxValue)
        throw new Win32Exception();
    // Allocate the required bytes and obtain the DACL
    if (!GetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION,
    psd = new byte[bufSizeNeeded], bufSizeNeeded, out bufSizeNeeded))
        throw new Win32Exception();
    // Use the RawSecurityDescriptor class from System.Security.AccessControl to parse the bytes:
    return new RawSecurityDescriptor(psd, 0);
}


//Updating the process DACL
[DllImport("advapi32.dll", SetLastError = true)]
static extern bool SetKernelObjectSecurity(IntPtr Handle, int securityInformation, [In] byte[] pSecurityDescriptor);
public static void SetProcessSecurityDescriptor(IntPtr processHandle, RawSecurityDescriptor dacl)
{
    const int DACL_SECURITY_INFORMATION = 0x00000004;
    byte[] rawsd = new byte[dacl.BinaryLength];
    dacl.GetBinaryForm(rawsd, 0);
    if (!SetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, rawsd))
        throw new Win32Exception();
}


//Getting the current process
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentProcess();


//Process access rights
[Flags]
public enum ProcessAccessRights
{
    PROCESS_CREATE_PROCESS = 0x0080, //  Required to create a process.
    PROCESS_CREATE_THREAD = 0x0002, //  Required to create a thread.
    PROCESS_DUP_HANDLE = 0x0040, // Required to duplicate a handle using DuplicateHandle.
    PROCESS_QUERY_INFORMATION = 0x0400, //  Required to retrieve certain information about a process, such as its token, exit code, and priority class (see OpenProcessToken, GetExitCodeProcess, GetPriorityClass, and IsProcessInJob).
    PROCESS_QUERY_LIMITED_INFORMATION = 0x1000, //  Required to retrieve certain information about a process (see QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted PROCESS_QUERY_LIMITED_INFORMATION. Windows Server 2003 and Windows XP/2000:  This access right is not supported.
    PROCESS_SET_INFORMATION = 0x0200, //    Required to set certain information about a process, such as its priority class (see SetPriorityClass).
    PROCESS_SET_QUOTA = 0x0100, //  Required to set memory limits using SetProcessWorkingSetSize.
    PROCESS_SUSPEND_RESUME = 0x0800, // Required to suspend or resume a process.
    PROCESS_TERMINATE = 0x0001, //  Required to terminate a process using TerminateProcess.
    PROCESS_VM_OPERATION = 0x0008, //   Required to perform an operation on the address space of a process (see VirtualProtectEx and WriteProcessMemory).
    PROCESS_VM_READ = 0x0010, //    Required to read memory in a process using ReadProcessMemory.
    PROCESS_VM_WRITE = 0x0020, //   Required to write to memory in a process using WriteProcessMemory.
    DELETE = 0x00010000, // Required to delete the object.
    READ_CONTROL = 0x00020000, //   Required to read information in the security descriptor for the object, not including the information in the SACL. To read or write the SACL, you must request the ACCESS_SYSTEM_SECURITY access right. For more information, see SACL Access Right.
    SYNCHRONIZE = 0x00100000, //    The right to use the object for synchronization. This enables a thread to wait until the object is in the signaled state.
    WRITE_DAC = 0x00040000, //  Required to modify the DACL in the security descriptor for the object.
    WRITE_OWNER = 0x00080000, //    Required to change the owner in the security descriptor for the object.
    STANDARD_RIGHTS_REQUIRED = 0x000f0000,
    PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF),//    All possible access rights for a process object.
}

public Form1()
{
        InitializeComponent();

        //Put it all together to prevent users from killing your service or process
        IntPtr hProcess = GetCurrentProcess(); // Get the current process handle
        // Read the DACL
        var dacl = GetProcessSecurityDescriptor(hProcess);
        // Insert the new ACE
        dacl.DiscretionaryAcl.InsertAce(
        0,
        new CommonAce(
        AceFlags.None,
        AceQualifier.AccessDenied,
        (int)ProcessAccessRights.PROCESS_ALL_ACCESS,
        new SecurityIdentifier(WellKnownSidType.WorldSid, null),
        false,
        null)
        );
        // Save the DACL
        SetProcessSecurityDescriptor(hProcess, dacl);
}

Reference: How to prevent users from killing your service or process

swartkatt
  • 368
  • 3
  • 10