36

I have a requirement to hide a process in Task Manager. It is for Intranet scenario. So, everything is legitimate. :)

Please feel free to share any code you have (preferably in C#) or any other techniques or any issues in going with this route.

Update1: Most of the users have admin privileges in order to run some legacy apps. So, one of the suggestion was to hide it in task manager. If there are other approaches to prevent users from killing the process, that would be great.

Update2: Removing the reference to rootkit. Somehow made this post look negative.

Gulzar Nazim
  • 51,744
  • 26
  • 128
  • 170
  • How is it being on an intranet a legitimate scenario? Don't give them admin priveliges... – Omar Kooheji Oct 09 '08 at 16:20
  • 1
    Thats the problem. Most of the users have admin privileges to support some legacy apps. – Gulzar Nazim Oct 09 '08 at 16:22
  • 16
    If users have administrator privileges, they OWN the machine, end of story. – Joel Coehoorn Oct 09 '08 at 16:34
  • 2
    I am curious why they would kill it to begin with? I mean, assuming (hehe) the machine runs well, why would I kill a process? Also, what does your program do? Is this to spy on the people at those machines? I can't think of any legit reason for this, but please enlighten me. – Till Oct 09 '08 at 16:39
  • 2
    Valid question. Think of an app like a virus scanner. I cannot disclose the exact reason but we have to keep this running all the time to actually help users. – Gulzar Nazim Oct 09 '08 at 17:33
  • 2
    We just want to prevent some power users from accidentaly killing this process. Need to cover this possibility from a technical point of view. – Gulzar Nazim Oct 09 '08 at 17:35
  • Could you elaborate on why you need this? It sounds like 'security by obscurity' and that, my friend, just doesn't work. – Sergio Acosta Oct 09 '08 at 16:21

16 Answers16

73

Don't try to stop it from being killed - you're not going to manage it. Instead, make it regularly call home to a webservice. When the webservice notices a client "going silent" it can ping the machine to see if it's just a reboot issue, and send an email to a manager (or whoever) to discipline whoever has killed the process.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    +100, I initially was looking to do what the original author wanted to do, but this sounds like an excellent idea. – Zack Jan 15 '10 at 19:54
50

There is no supported way to accomplish this. The process list can be read at any privilege level. If you were hoping to hide a process from even Administrators, then this is doubly unsupported.

To get this to work, you would need to write a kernel mode rootkit to intercept calls to NtQuerySystemInformation so that the SystemProcessInformation info class fails to list your hidden process.

Intercepting system calls is very difficult to do safely, and the 64 bit Windows kernels go out of their way to prevent this from being possible: trying to modify the syscall table results in an instant blue screen. It's going to be very difficult on those platforms

Here is an example of a rootkit that tries to do something similar (and has several serious problems).

Chris Smith
  • 5,326
  • 29
  • 29
  • The last link you have provided is no longer available. Can you please find the original thing and post it please? :) – Eru Aug 19 '23 at 20:15
19

If you want to prevent users from killing the process from task manager, you can just use a security descriptor on the process to deny terminate access to everyone. Administrators technically can still kill the process by taking ownership of the process and resetting the DACL, but there is no interface to do either of these things from Task Manager. Process Explorer may have an interface to though.

When your process starts, use SetKernelObjectSecurity with DACL_SECURITY_INFORMATION using the current process handle. Set a DACL with zero ACLs. This will deny all access to everyone, including those trying to end your process with task manager.

Here is an example that also changes the process's owner:

SECURITY_DESCRIPTOR sd;
ACL dacl;
SID_IDENTIFIER_AUTHORITY ntauth = SECURITY_NT_AUTHORITY;
PSID owner;

assert(InitializeAcl(&dacl, sizeof dacl, ACL_REVISION));

assert(AllocateAndInitializeSid(&ntauth, 1, SECURITY_LOCAL_SYSTEM_RID, 0,0,0,0,0,0,0, &owner));

assert(InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION));

assert(SetSecurityDescriptorDacl(&sd, TRUE, &dacl, FALSE));

assert(SetSecurityDescriptorOwner(&sd, owner, FALSE));

assert(SetKernelObjectSecurity(GetCurrentProcess(), DACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, &sd));

assert(FreeSid(owner) == NULL);

Unfortunately, it doesn't seem to be effective. I can still close the process (although not as a limited user). Perhaps Task Manager is taking ownership or invoking some other privilege to kill the process? I seem to remember this working in previous versions of Windows (I was testing 2003), but I could be mistaken.

Chris Smith
  • 5,326
  • 29
  • 29
  • I've tried to do this in a sample app, but it didn't seem to have any effect (although all of the calls succeeded). I hate to say it, but can you link to some code that does this? – Stephen Deken Oct 09 '08 at 18:31
  • I was about to ask the same question. Thanks Stephen. – Gulzar Nazim Oct 09 '08 at 19:43
  • If you use `assert()` the code will fail when `NDEBUG` is defined (that macro is defined to nothing when that is defined). `assert()` doesn't replace error checking. – quantum Feb 20 '13 at 00:59
  • @xiaomao - I supplied a code snippet to illustrate usage of the relevant APIs. There are many different error handling strategies possible, putting in something more complicated would have obfuscated the code. `assert()` makes my meaning plain, which was the point; it wasn't meant to be pasted into an arbitrary codebase with no understanding. Besides, you can certainly use headers that enforce `assert()` in all builds. – Chris Smith Feb 22 '13 at 19:00
10

I hope that you would not be able to.

Update: given the scenario, I think that you will probably be best off running it under a different admin account. That may help alert people to the fact that they should not kill the process.

Marcin
  • 48,559
  • 18
  • 128
  • 201
7

Alternatively, you could write a small "checker" utility that checks if the app is running, if it isn't it automatically starts it. Then add code to the app to check for the "checker" utility that does the same. This way if one is terminated, then the other starts it back up. I've seem virus's do this, and it seems to work pretty effectively.

Chris Pietschmann
  • 29,502
  • 35
  • 121
  • 166
6

Write a driver - You can use ObRegisterCallbacks to register process object access notification. Return STATUS_ACCESS_DENIED when DesiredAccess contains access rights you don't like, like process termination, or writing to process memory.

http://msdn.microsoft.com/en-us/library/windows/hardware/ff558692(v=vs.85).aspx

Uriel G.
  • 61
  • 1
  • 1
4

Not sure why this hasnt been suggested yet but heres my first answer on this site. instead of preventing the user from killing a process. (Requires rootkit hooking.) You can simply disable the task manager from being used with a registry input.

public static void ToggleTaskManager(bool toggle)
{
    Microsoft.Win32.RegistryKey HKCU = Microsoft.Win32.Registry.LocalMachine;
    Microsoft.Win32.RegistryKey key = HKCU.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Policies\System");
    key.SetValue("DisableTaskMgr", toggle ? 0 : 1, Microsoft.Win32.RegistryValueKind.DWord);
}
Cacoon
  • 2,467
  • 6
  • 28
  • 61
  • 3
    And what about `tasklist.exe`, `taskkill.exe´ or `ProcessMonitor`? So simply preventing the TaskManager seems not being a good way of preventing closing a process. – Oliver Jan 11 '13 at 13:31
  • 6
    You could always use your application to kill those aswell. – Cacoon Jan 11 '13 at 13:34
4

If you simply need to disguise the process and not hide it completely, you can rename it winlogon.exe or svchost.exe and it will likely be ignored by users. But as Sergio mentioned, that's security by obscurity and it's got a bad reputation for a reason.

Preventing users from killing a process is another difficulty if they have proper privileges. The only method I know is to have multiple processes that watch each other and restart any watched process which gets killed. Again, this is going down a shady path.

Ben Hoffstein
  • 102,129
  • 8
  • 104
  • 120
  • I hate to say it, but since users have admin privileges disguising the process is probably his best bet. Be wary, though: anti-virus software may see this as malicious behavior and just block the program. – Joel Coehoorn Oct 09 '08 at 16:35
3

Many people might know how to do it, but just won't post it here. It is very dangerous to post malicious code on the internet. Who knows you might be in danger. Ask some computer engineer. I'll give you the structure of the program though.

Just inject your program's dll into explorer.exe.

Your process won't just show up because it is not running as a program but is being ran in a program (explorer.exe). The user just won't see the process even if he uses any kind of task manager.

Sam
  • 7,252
  • 16
  • 46
  • 65
ABCDWHAT
  • 31
  • 4
3

There is no easy or supported way to do this. Even if you wrote a rootkit to do it then that could very easily get broken by a future update that was made to plug that hole. I would reexamine whether that is something you want to do.

StubbornMule
  • 2,720
  • 5
  • 20
  • 19
3

As people mentioned above, the best method is 2 tasks, monitoring each other, I understand you don't want to waste CPU, so the best way is to establish an event between the tasks which will be triggered when one closes.

I am not entirely sure on how to set up the hook, but then you don't use a while loop which does waste CPU.

eitama
  • 1,477
  • 14
  • 16
2

Have you looked at writing a service? This way the service runs as the local system, and the application runs under the user's context, and the service can ensure that things are still done as needed, and the application is just an interface to this service. Killing the application would just result in the user not seeing any notices, system tray icon, etc, but the service is still doing its job.

Rick
  • 21
  • 1
2

I saw @Chris Smith answer and I decided to convert it to C#.

Here is the code, taken from here, for a simple Winform application:
C# variation:

   using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Security.AccessControl;
    using System.Security.Principal;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;

namespace Hide2
{
    public partial class Form1 : Form
    {
        [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);
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool SetKernelObjectSecurity(IntPtr Handle, int securityInformation, [In] byte[] pSecurityDescriptor);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetCurrentProcess();

        [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 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();
        }

        public Form1()
        {
            InitializeComponent();

            // Get the current process handle
            IntPtr hProcess = GetCurrentProcess();
            // 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);
        }
    }
}

After running it as a limited user, I can't kill it from the task manager, only as administrator.
I left the X button to be able to close it without an admin but it also possible to remove it.

The result:

enter image description here

Powershell variation:

$source = @"
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;

namespace Hide2
{
    public class myForm
    {
        [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);
        }

        [DllImport("advapi32.dll", SetLastError = true)]
        static extern bool SetKernelObjectSecurity(IntPtr Handle, int securityInformation, [In] byte[] pSecurityDescriptor);

        [DllImport("kernel32.dll")]
        public static extern IntPtr GetCurrentProcess();

        [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 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();
        }

        public static void ProtectMyProcess()
        {
            // Get the current process handle
            IntPtr hProcess = GetCurrentProcess();
            // 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);

        }
    }
}
"@

Add-Type -TypeDefinition $Source -Language CSharp  

[ScriptBlock]$scriptNewForm = {
    Add-Type -AssemblyName System.Windows.Forms

    $Form = New-Object system.Windows.Forms.Form
    $Form.Text = "PowerShell form"
    $Form.TopMost = $true
    $Form.Width = 303
    $Form.Height = 274

    [void]$Form.ShowDialog()
    $Form.Dispose()
}



$SleepTimer = 200
$MaxResultTime = 120
$MaxThreads = 3

$ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
$RunspacePool.Open()

$Jobs = @()

$PowershellThread = [powershell]::Create().AddScript($scriptNewForm)
$PowershellThread.RunspacePool = $RunspacePool
$Handle = $PowershellThread.BeginInvoke()
$Job = "" | Select-Object Handle, Thread, object
$Job.Handle = $Handle
$Job.Thread = $PowershellThread
$Job.Object = $computer
$Jobs += $Job

[Hide2.myForm]::ProtectMyProcess()

<#
ForEach ($Job in $Jobs){
    $Job.Thread.EndInvoke($Job.Handle)
    $Job.Thread.Dispose()
    $Job.Thread = $Null
    $Job.Handle = $Null
}
#>
E235
  • 11,560
  • 24
  • 91
  • 141
1

What about you just ask the user to don't kill the process ? How much time you'll spend doing it, for a behavior that is clearly childish from employees in the same company.

pmlarocque
  • 1,704
  • 1
  • 18
  • 26
1

I know this question is old but I answered a duplicate question a while ago that contains some nice information there that isn't here so I thought I'd link to it. See My answer to the duplicate question. Also if your true goal is to stop the users from killing the process then what I know used to work very easily, although it is a bit hackish and I don't know if this still works, is to simply name your application lsass.exe and task manager will not allow even an admin user to close the process. for this method it doesn't matter which user started the process or where the executable resides on the file system, it seems windows just checks to see if the process is named this then do not allow it to be ended.

Update: I just tried to do the lsass.exe trick on windows 7 and it appears to have been fixed, but my guess is it still works on windows xp and maybe even the earlier service packs of the versions beyond xp. Even though this no longer works at the time of this writing I thought I'd include it anyway as a fun fact.

Community
  • 1
  • 1
MitchellKrenz
  • 423
  • 4
  • 14
-1

to stop the process from being permanently killed, have the first thing the process does is to call 'atexit()' and have the atexit() function start the process

user3629249
  • 16,402
  • 1
  • 16
  • 17