Before I say another word, I have read “The arms race between programs and users” and wholeheartedly agree. You can not, and should not, attempt to stop an Administrator from killing your process or stopping your service. That is not what we are trying to do here, we are trying to prevent Joe User from disrupting our process.
So let’s get started, to do this we want to adjust our process’ access control list. The individual rights we can grant and deny are discussed on MSDN in the articled titled “Process Security and Access Rights“. Though most of that article is about creating a process with specific rights, in this case we want to modify the rights of our current process. To do this we are going to PInvoke GetKernelObjectSecurity to obtain the DACL (Discretionary Access Control List), modify it using theRawSecurityDescriptor, and finally write it back using the SetKernelObjectSecurity API. This should be somewhat familiar to those of you that followed that part of building our service.
Step 1 – 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);
}
And you thought that was going to be hard? Of course we also need to be able to save the DACL. So…
Part 2 – 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();
}
Cool, that was easy, so we are ready now right? Not quite, we still need to get the process handle. A simple thing for our own process:
Part 3 – Getting the current process
[DllImport(
"kernel32.dll"
)]
public
static
extern
IntPtr GetCurrentProcess();
It just gets easier and easier. Since this is the current process there (AFAIK) is not any reason to duplicate handle and request additional permissions. This handle should have access to do anything. So the only thing left before we can modify the permissions is to define what those permissions are.
Part 4 – 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.
}
Part 5 – Put it all together
// 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);
The ace we’ve added now will try to deny access to the “Everyone” group (aka WorldSid) to do anything with our process. We need not fear that we are interrupting an administrator’s ability to perform any action they desire. They will have any access required that’s why we call them administrators :) As I said in the beginning, we are not trying to stop Administrators, we just want to make sure they are an administrator if they plan to modify or kill this process.
We could of course be more discrete about the rights we are trying to deny, that is entirely up to you.
沒有留言:
張貼留言