mirror of
https://github.com/mirror/processhacker
synced 2026-06-08 16:03:24 +00:00
7bd28e4357
git-svn-id: svn://svn.code.sf.net/p/processhacker/code@2304 21ef857c-d57f-4fe0-8362-d861dc6d29cd
458 lines
13 KiB
C
458 lines
13 KiB
C
/*
|
|
* Process Hacker Driver -
|
|
* process protection
|
|
*
|
|
* Copyright (C) 2009 wj32
|
|
*
|
|
* This file is part of Process Hacker.
|
|
*
|
|
* Process Hacker is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Process Hacker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "include/protect.h"
|
|
|
|
BOOLEAN KphpIsAccessAllowed(
|
|
__in PVOID Object,
|
|
__in KPROCESSOR_MODE AccessMode,
|
|
__in ACCESS_MASK DesiredAccess
|
|
);
|
|
|
|
BOOLEAN KphpIsCurrentProcessProtected();
|
|
|
|
VOID KphpProtectRemoveEntry(
|
|
__in PKPH_PROCESS_ENTRY Entry
|
|
);
|
|
|
|
/* ProtectedProcessRundownProtect
|
|
*
|
|
* Rundown protection making sure this module doesn't deinitialize before all hook targets
|
|
* have finished executing and no one is accessing the lookaside list.
|
|
*/
|
|
static EX_RUNDOWN_REF ProtectedProcessRundownProtect;
|
|
/* ProtectedProcessListHead
|
|
*
|
|
* The head of the process protection linked list. Each entry stores protection
|
|
* information for a process.
|
|
*/
|
|
static LIST_ENTRY ProtectedProcessListHead;
|
|
/* ProtectedProcessListLock
|
|
*
|
|
* The spinlock which protects all accesses to the protected process list (even
|
|
* the individual entries)
|
|
*/
|
|
static KSPIN_LOCK ProtectedProcessListLock;
|
|
/* ProtectedProcessLookasideList
|
|
*
|
|
* The lookaside list for protected process entries.
|
|
*/
|
|
static NPAGED_LOOKASIDE_LIST ProtectedProcessLookasideList;
|
|
|
|
static KPH_OB_OPEN_HOOK ProcessOpenHook = { 0 };
|
|
static KPH_OB_OPEN_HOOK ThreadOpenHook = { 0 };
|
|
|
|
/* KphProtectInit
|
|
*
|
|
* Initializes process protection.
|
|
*
|
|
* IRQL: <= APC_LEVEL
|
|
*/
|
|
NTSTATUS KphProtectInit()
|
|
{
|
|
NTSTATUS status;
|
|
|
|
/* Initialize rundown protection. */
|
|
ExInitializeRundownProtection(&ProtectedProcessRundownProtect);
|
|
/* Initialize list structures. */
|
|
InitializeListHead(&ProtectedProcessListHead);
|
|
KeInitializeSpinLock(&ProtectedProcessListLock);
|
|
ExInitializeNPagedLookasideList(
|
|
&ProtectedProcessLookasideList,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
sizeof(KPH_PROCESS_ENTRY),
|
|
TAG_PROTECTION_ENTRY,
|
|
0
|
|
);
|
|
|
|
/* Hook various functions. */
|
|
/* Hooking the open procedure calls for processes and threads allows
|
|
* us to intercept handle creation/duplication/inheritance. */
|
|
KphInitializeObOpenHook(&ProcessOpenHook, *PsProcessType, KphNewOpenProcedure51, KphNewOpenProcedure60);
|
|
if (!NT_SUCCESS(status = KphObOpenHook(&ProcessOpenHook)))
|
|
return status;
|
|
KphInitializeObOpenHook(&ThreadOpenHook, *PsThreadType, KphNewOpenProcedure51, KphNewOpenProcedure60);
|
|
if (!NT_SUCCESS(status = KphObOpenHook(&ThreadOpenHook)))
|
|
return status;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* KphProtectDeinit
|
|
*
|
|
* Removes process protection and frees associated structures.
|
|
*
|
|
* IRQL: <= APC_LEVEL
|
|
*/
|
|
NTSTATUS KphProtectDeinit()
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
KIRQL oldIrql;
|
|
LARGE_INTEGER waitLi;
|
|
|
|
/* Unhook. */
|
|
status = KphObOpenUnhook(&ProcessOpenHook);
|
|
status = KphObOpenUnhook(&ThreadOpenHook);
|
|
|
|
/* Wait for all activity to finish. */
|
|
ExWaitForRundownProtectionRelease(&ProtectedProcessRundownProtect);
|
|
/* Wait for a bit (some regions of hook target functions
|
|
are NOT guarded by rundown protection, e.g.
|
|
prologues and epilogues). */
|
|
waitLi.QuadPart = KPH_REL_TIMEOUT_IN_SEC(1);
|
|
KeDelayExecutionThread(KernelMode, FALSE, &waitLi);
|
|
|
|
/* Free all process protection entries. */
|
|
ExDeleteNPagedLookasideList(&ProtectedProcessLookasideList);
|
|
|
|
return status;
|
|
}
|
|
|
|
/* KphNewOpenProcedure51
|
|
*
|
|
* New process/thread open procedure for NT 5.1.
|
|
*/
|
|
NTSTATUS NTAPI KphNewOpenProcedure51(
|
|
__in OB_OPEN_REASON OpenReason,
|
|
__in PEPROCESS Process,
|
|
__in PVOID Object,
|
|
__in ACCESS_MASK GrantedAccess,
|
|
__in ULONG HandleCount
|
|
)
|
|
{
|
|
/* Simply call the 6.0 open procedure. */
|
|
/* NOTE: GrantedAccess is always 0 on XP... */
|
|
return KphNewOpenProcedure60(
|
|
OpenReason,
|
|
/* Assume worst case. */
|
|
UserMode,
|
|
Process,
|
|
Object,
|
|
GrantedAccess,
|
|
HandleCount
|
|
);
|
|
}
|
|
|
|
/* KphNewOpenProcedure60
|
|
*
|
|
* New process/thread open procedure for NT 6.0 and 6.1.
|
|
*/
|
|
NTSTATUS NTAPI KphNewOpenProcedure60(
|
|
__in OB_OPEN_REASON OpenReason,
|
|
__in KPROCESSOR_MODE AccessMode,
|
|
__in PEPROCESS Process,
|
|
__in PVOID Object,
|
|
__in ACCESS_MASK GrantedAccess,
|
|
__in ULONG HandleCount
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
BOOLEAN accessAllowed = TRUE;
|
|
|
|
/* Prevent the driver from unloading while this routine is executing. */
|
|
if (!ExAcquireRundownProtection(&ProtectedProcessRundownProtect))
|
|
{
|
|
/* Should never happen. */
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
accessAllowed = KphpIsAccessAllowed(
|
|
Object,
|
|
AccessMode,
|
|
/* Assume worst case if granted access not available. */
|
|
!GrantedAccess ? (ACCESS_MASK)-1 : GrantedAccess
|
|
);
|
|
|
|
if (accessAllowed)
|
|
{
|
|
POBJECT_TYPE objectType = KphGetObjectTypeNt(Object);
|
|
|
|
/* Call the original open procedure. There shouldn't be any for Windows XP,
|
|
* while on Windows Vista and 7 it is used for implementing protected
|
|
* processes (Big Content's DRM protection, not KProcessHacker's protection).
|
|
*/
|
|
status = KphObOpenCall(
|
|
objectType == *PsProcessType ? &ProcessOpenHook : &ThreadOpenHook,
|
|
OpenReason,
|
|
AccessMode,
|
|
Process,
|
|
Object,
|
|
GrantedAccess,
|
|
HandleCount
|
|
);
|
|
}
|
|
else
|
|
{
|
|
dprintf("KphNewOpenProcedure60: Access denied.\n");
|
|
status = STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
ExReleaseRundownProtection(&ProtectedProcessRundownProtect);
|
|
|
|
return status;
|
|
}
|
|
|
|
/* KphProtectAddEntry
|
|
*
|
|
* Protects the specified process.
|
|
*
|
|
* Thread safety: Full
|
|
* IRQL: <= DISPATCH_LEVEL
|
|
*/
|
|
PKPH_PROCESS_ENTRY KphProtectAddEntry(
|
|
__in PEPROCESS Process,
|
|
__in HANDLE Tag,
|
|
__in LOGICAL AllowKernelMode,
|
|
__in ACCESS_MASK ProcessAllowMask,
|
|
__in ACCESS_MASK ThreadAllowMask
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
PKPH_PROCESS_ENTRY entry;
|
|
|
|
/* Prevent the lookaside list from being freed. */
|
|
if (!ExAcquireRundownProtection(&ProtectedProcessRundownProtect))
|
|
return NULL;
|
|
|
|
entry = ExAllocateFromNPagedLookasideList(&ProtectedProcessLookasideList);
|
|
/* Lookaside list no longer needed. */
|
|
ExReleaseRundownProtection(&ProtectedProcessRundownProtect);
|
|
|
|
if (!entry)
|
|
return NULL;
|
|
|
|
entry->Process = Process;
|
|
entry->CreatorProcess = PsGetCurrentProcess();
|
|
entry->Tag = Tag;
|
|
entry->AllowKernelMode = AllowKernelMode;
|
|
entry->ProcessAllowMask = ProcessAllowMask;
|
|
entry->ThreadAllowMask = ThreadAllowMask;
|
|
|
|
KeAcquireSpinLock(&ProtectedProcessListLock, &oldIrql);
|
|
InsertHeadList(&ProtectedProcessListHead, &entry->ListEntry);
|
|
KeReleaseSpinLock(&ProtectedProcessListLock, oldIrql);
|
|
|
|
return entry;
|
|
}
|
|
|
|
/* KphProtectFindEntry
|
|
*
|
|
* Finds process protection data.
|
|
*
|
|
* Thread safety: Full/Limited. The returned pointer is not guaranteed to
|
|
* point to a valid process entry. However, the copied entry is safe to
|
|
* read.
|
|
* IRQL: <= DISPATCH_LEVEL
|
|
*/
|
|
PKPH_PROCESS_ENTRY KphProtectFindEntry(
|
|
__in PEPROCESS Process,
|
|
__in HANDLE Tag,
|
|
__out_opt PKPH_PROCESS_ENTRY ProcessEntryCopy
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
PLIST_ENTRY entry = ProtectedProcessListHead.Flink;
|
|
|
|
KeAcquireSpinLock(&ProtectedProcessListLock, &oldIrql);
|
|
|
|
while (entry != &ProtectedProcessListHead)
|
|
{
|
|
PKPH_PROCESS_ENTRY processEntry =
|
|
CONTAINING_RECORD(entry, KPH_PROCESS_ENTRY, ListEntry);
|
|
|
|
if (
|
|
(Process != NULL && processEntry->Process == Process) ||
|
|
(Tag != NULL && processEntry->Tag == Tag)
|
|
)
|
|
{
|
|
/* Copy the entry if requested. */
|
|
if (ProcessEntryCopy)
|
|
memcpy(ProcessEntryCopy, processEntry, sizeof(KPH_PROCESS_ENTRY));
|
|
|
|
KeReleaseSpinLock(&ProtectedProcessListLock, oldIrql);
|
|
|
|
return processEntry;
|
|
}
|
|
|
|
entry = entry->Flink;
|
|
}
|
|
|
|
KeReleaseSpinLock(&ProtectedProcessListLock, oldIrql);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* KphProtectRemoveByProcess
|
|
*
|
|
* Removes protection from the specified process.
|
|
*
|
|
* Thread safety: Limited. Callers must synchronize remove calls such
|
|
* as KphProtectRemoveByProcess and KphProtectRemoveByTag.
|
|
* IRQL: <= DISPATCH_LEVEL
|
|
*/
|
|
BOOLEAN KphProtectRemoveByProcess(
|
|
__in PEPROCESS Process
|
|
)
|
|
{
|
|
PKPH_PROCESS_ENTRY entry = KphProtectFindEntry(Process, NULL, NULL);
|
|
|
|
if (!entry)
|
|
return FALSE;
|
|
|
|
KphpProtectRemoveEntry(entry);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* KphProtectRemoveByTag
|
|
*
|
|
* Removes protection from all processes with the specified tag.
|
|
*
|
|
* Thread safety: Limited. Callers must synchronize remove calls such
|
|
* as KphProtectRemoveByProcess and KphProtectRemoveByTag.
|
|
* IRQL: <= DISPATCH_LEVEL
|
|
*/
|
|
ULONG KphProtectRemoveByTag(
|
|
__in HANDLE Tag
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
ULONG count = 0;
|
|
PKPH_PROCESS_ENTRY entry;
|
|
|
|
/* Keep removing entries until we can't find any more. */
|
|
while (entry = KphProtectFindEntry(NULL, Tag, NULL))
|
|
{
|
|
KphpProtectRemoveEntry(entry);
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/* KphpIsAccessAllowed
|
|
*
|
|
* Checks if the specified access is allowed, according to process
|
|
* protection rules.
|
|
*
|
|
* Thread safety: Full
|
|
* IRQL: <= DISPATCH_LEVEL
|
|
*/
|
|
BOOLEAN KphpIsAccessAllowed(
|
|
__in PVOID Object,
|
|
__in KPROCESSOR_MODE AccessMode,
|
|
__in ACCESS_MASK DesiredAccess
|
|
)
|
|
{
|
|
POBJECT_TYPE objectType;
|
|
PEPROCESS processObject;
|
|
BOOLEAN isThread = FALSE;
|
|
|
|
objectType = KphGetObjectTypeNt(Object);
|
|
/* It doesn't matter if it isn't actually a process because we won't be
|
|
dereferencing it. */
|
|
processObject = (PEPROCESS)Object;
|
|
isThread = objectType == *PsThreadType;
|
|
|
|
/* If this is a thread, get its parent process. */
|
|
if (isThread)
|
|
processObject = IoThreadToProcess((PETHREAD)Object);
|
|
|
|
if (
|
|
processObject != PsGetCurrentProcess() && /* let the caller open its own processes/threads */
|
|
(objectType == *PsProcessType || objectType == *PsThreadType) /* only protect processes and threads */
|
|
)
|
|
{
|
|
KPH_PROCESS_ENTRY processEntry;
|
|
|
|
/* Search for and copy the corresponding process protection entry. */
|
|
if (KphProtectFindEntry(processObject, NULL, &processEntry))
|
|
{
|
|
ACCESS_MASK mask =
|
|
isThread ? processEntry.ThreadAllowMask : processEntry.ProcessAllowMask;
|
|
|
|
/* The process/thread is protected. Check if the requested access is allowed. */
|
|
if (
|
|
/* check if kernel-mode is exempt from protection */
|
|
!(processEntry.AllowKernelMode && AccessMode == KernelMode) &&
|
|
/* allow the creator of the rule to bypass protection */
|
|
processEntry.CreatorProcess != PsGetCurrentProcess() &&
|
|
(DesiredAccess & mask) != DesiredAccess
|
|
)
|
|
{
|
|
/* Access denied. */
|
|
dprintf(
|
|
"%d: Access denied: 0x%08x (%s)\n",
|
|
PsGetCurrentProcessId(),
|
|
DesiredAccess,
|
|
isThread ? "Thread" : "Process"
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* KphpIsCurrentProcessProtected
|
|
*
|
|
* Determines whether the current process is protected.
|
|
*
|
|
* Thread safety: Full
|
|
* IRQL: <= DISPATCH_LEVEL
|
|
*/
|
|
BOOLEAN KphpIsCurrentProcessProtected()
|
|
{
|
|
return KphProtectFindEntry(PsGetCurrentProcess(), NULL, NULL) != NULL;
|
|
}
|
|
|
|
/* KphpProtectRemoveEntry
|
|
*
|
|
* Removes and frees process protection data.
|
|
*
|
|
* Thread safety: Full
|
|
* IRQL: <= DISPATCH_LEVEL
|
|
*/
|
|
VOID KphpProtectRemoveEntry(
|
|
__in PKPH_PROCESS_ENTRY Entry
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
|
|
KeAcquireSpinLock(&ProtectedProcessListLock, &oldIrql);
|
|
RemoveEntryList(&Entry->ListEntry);
|
|
|
|
/* Prevent the lookaside list from being destroyed. */
|
|
ExAcquireRundownProtection(&ProtectedProcessRundownProtect);
|
|
ExFreeToNPagedLookasideList(
|
|
&ProtectedProcessLookasideList,
|
|
Entry
|
|
);
|
|
ExReleaseRundownProtection(&ProtectedProcessRundownProtect);
|
|
|
|
KeReleaseSpinLock(&ProtectedProcessListLock, oldIrql);
|
|
}
|