Files
2026-07-07 02:10:56 +08:00

2125 lines
74 KiB
C++

#include "DriverCommon.h"
constexpr auto MAX_TRACKERS = 128;
constexpr auto TRUST_CACHE_SIZE = 128;
constexpr auto TRUST_CACHE_TTL_SEC = 300;
constexpr auto MAX_RULE_FILE_SIZE = 4 * 1024 * 1024;
typedef struct _BEHAVIOR_TRACKER {
PEPROCESS Process;
PDYNAMIC_RULE Rule;
LARGE_INTEGER ProcessCreateTime;
ULONG ActivityCount;
LARGE_INTEGER LastActivityTime;
} BEHAVIOR_TRACKER, * PBEHAVIOR_TRACKER;
typedef struct _TRUST_CACHE_ENTRY {
PEPROCESS Process;
LARGE_INTEGER ProcessCreateTime;
BOOLEAN IsTrusted;
LARGE_INTEGER CacheTime;
} TRUST_CACHE_ENTRY, * PTRUST_CACHE_ENTRY;
typedef struct _RULE_TERMINATION_WORK_ITEM {
LIST_ENTRY Entry;
PEPROCESS Process;
} RULE_TERMINATION_WORK_ITEM, * PRULE_TERMINATION_WORK_ITEM;
static BEHAVIOR_TRACKER BehaviorTrackers[MAX_TRACKERS];
static TRUST_CACHE_ENTRY TrustCache[TRUST_CACHE_SIZE];
static KSPIN_LOCK TrustCacheLock;
static BOOLEAN g_CacheInitialized = FALSE;
static ERESOURCE RuleLock;
static PDYNAMIC_RULE g_DynamicRules = NULL;
static PRULE_NODE g_ProcessTrustedPaths = NULL;
static LIST_ENTRY g_TerminationQueue;
static KSPIN_LOCK g_TerminationLock;
static KSEMAPHORE g_TerminationSemaphore;
static HANDLE g_TerminationWorkerHandle = NULL;
static volatile LONG g_TerminationStopping = 1;
static volatile LONG g_PendingTerminations = 0;
constexpr auto MAX_PENDING_TERMINATIONS = 128;
static BOOLEAN CheckRuleThreshold(PDYNAMIC_RULE Rule, HANDLE ProcessId);
BOOLEAN IsAddressInUnmappedMemory(HANDLE ProcessId, PVOID Address);
ULONG SafeGetPyasPid() {
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) return GlobalData.PyasPid;
KIRQL OldIrql;
KeAcquireSpinLock(&GlobalData.PidLock, &OldIrql);
ULONG Pid = GlobalData.PyasPid;
KeReleaseSpinLock(&GlobalData.PidLock, OldIrql);
return Pid;
}
VOID SafeSetPyasPid(ULONG Pid) {
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) return;
KIRQL OldIrql;
KeAcquireSpinLock(&GlobalData.PidLock, &OldIrql);
GlobalData.PyasPid = Pid;
KeReleaseSpinLock(&GlobalData.PidLock, OldIrql);
}
static BOOLEAN IsProtectedTerminationTarget(PEPROCESS Process) {
if (!Process || Process == PsInitialSystemProcess) return TRUE;
BOOLEAN IsConnectedClient = FALSE;
KIRQL OldIrql;
KeAcquireSpinLock(&GlobalData.PortMutex, &OldIrql);
IsConnectedClient = GlobalData.PyasProcess == Process;
KeReleaseSpinLock(&GlobalData.PortMutex, OldIrql);
if (IsConnectedClient) return TRUE;
PUNICODE_STRING ImagePath = NULL;
NTSTATUS Status = SeLocateProcessImageName(Process, &ImagePath);
if (!NT_SUCCESS(Status) || !ImagePath || !ImagePath->Buffer || ImagePath->Length == 0) {
if (ImagePath) ExFreePool(ImagePath);
return TRUE;
}
USHORT CharacterCount = ImagePath->Length / sizeof(WCHAR);
LONG LastSeparator = -1;
LONG ParentSeparator = -1;
for (LONG Index = CharacterCount - 1; Index >= 0; Index--) {
if (ImagePath->Buffer[Index] != L'\\') continue;
if (LastSeparator < 0) LastSeparator = Index;
else {
ParentSeparator = Index;
break;
}
}
BOOLEAN Protected = TRUE;
if (LastSeparator > 0 && ParentSeparator >= 0 && LastSeparator + 1 < CharacterCount) {
UNICODE_STRING DirectoryName = {
(USHORT)((LastSeparator - ParentSeparator - 1) * sizeof(WCHAR)),
(USHORT)((LastSeparator - ParentSeparator - 1) * sizeof(WCHAR)),
ImagePath->Buffer + ParentSeparator + 1
};
UNICODE_STRING FileName = {
(USHORT)((CharacterCount - LastSeparator - 1) * sizeof(WCHAR)),
(USHORT)((CharacterCount - LastSeparator - 1) * sizeof(WCHAR)),
ImagePath->Buffer + LastSeparator + 1
};
UNICODE_STRING System32Name = RTL_CONSTANT_STRING(L"System32");
if (!RtlEqualUnicodeString(&DirectoryName, &System32Name, TRUE)) {
Protected = FALSE;
}
else {
PCWSTR CriticalNames[] = {
L"csrss.exe",
L"smss.exe",
L"wininit.exe",
L"winlogon.exe",
L"services.exe",
L"lsass.exe",
L"svchost.exe",
L"fontdrvhost.exe",
L"conhost.exe"
};
Protected = FALSE;
for (ULONG Index = 0; Index < RTL_NUMBER_OF(CriticalNames); Index++) {
UNICODE_STRING CriticalName;
RtlInitUnicodeString(&CriticalName, CriticalNames[Index]);
if (RtlEqualUnicodeString(&FileName, &CriticalName, TRUE)) {
Protected = TRUE;
break;
}
}
}
}
ExFreePool(ImagePath);
return Protected;
}
static VOID ReleaseRuleTerminationWork(PRULE_TERMINATION_WORK_ITEM Work) {
if (!Work) return;
if (Work->Process) ObDereferenceObject(Work->Process);
PyasFree(Work);
InterlockedDecrement(&g_PendingTerminations);
}
static PRULE_TERMINATION_WORK_ITEM PopRuleTerminationWork() {
PRULE_TERMINATION_WORK_ITEM Work = NULL;
KIRQL OldIrql;
KeAcquireSpinLock(&g_TerminationLock, &OldIrql);
if (!IsListEmpty(&g_TerminationQueue)) {
PLIST_ENTRY Entry = RemoveHeadList(&g_TerminationQueue);
Work = CONTAINING_RECORD(Entry, RULE_TERMINATION_WORK_ITEM, Entry);
}
KeReleaseSpinLock(&g_TerminationLock, OldIrql);
return Work;
}
static VOID CancelRuleTerminationWork() {
for (;;) {
PRULE_TERMINATION_WORK_ITEM Work = PopRuleTerminationWork();
if (!Work) break;
ReleaseRuleTerminationWork(Work);
}
}
static VOID TerminateRuleProcess(PEPROCESS Process) {
if (IsProtectedTerminationTarget(Process)) return;
HANDLE ProcessHandle = NULL;
NTSTATUS Status = ObOpenObjectByPointer(
Process,
OBJ_KERNEL_HANDLE,
NULL,
PROCESS_TERMINATE,
*PsProcessType,
KernelMode,
&ProcessHandle
);
if (!NT_SUCCESS(Status)) return;
ZwTerminateProcess(ProcessHandle, STATUS_ACCESS_DENIED);
ZwClose(ProcessHandle);
}
static VOID RuleTerminationWorkerThread(PVOID Parameter) {
UNREFERENCED_PARAMETER(Parameter);
for (;;) {
KeWaitForSingleObject(
&g_TerminationSemaphore,
Executive,
KernelMode,
FALSE,
NULL
);
if (InterlockedCompareExchange(&g_TerminationStopping, 0, 0) != 0) {
CancelRuleTerminationWork();
break;
}
PRULE_TERMINATION_WORK_ITEM Work = PopRuleTerminationWork();
if (!Work) continue;
TerminateRuleProcess(Work->Process);
ReleaseRuleTerminationWork(Work);
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
static NTSTATUS StartRuleTerminationWorker() {
if (g_TerminationWorkerHandle) return STATUS_SUCCESS;
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(
&ObjectAttributes,
NULL,
OBJ_KERNEL_HANDLE,
NULL,
NULL
);
return PsCreateSystemThread(
&g_TerminationWorkerHandle,
SYNCHRONIZE,
&ObjectAttributes,
NULL,
NULL,
RuleTerminationWorkerThread,
NULL
);
}
static VOID StopRuleTerminationWorker() {
CancelRuleTerminationWork();
if (!g_TerminationWorkerHandle) return;
KeReleaseSemaphore(&g_TerminationSemaphore, IO_NO_INCREMENT, 1, FALSE);
ZwWaitForSingleObject(g_TerminationWorkerHandle, FALSE, NULL);
ZwClose(g_TerminationWorkerHandle);
g_TerminationWorkerHandle = NULL;
}
VOID QueueRuleProcessTermination(HANDLE ProcessId, BOOLEAN Kill) {
if (!Kill || !ProcessId || (ULONG_PTR)ProcessId <= 4) return;
if (KeGetCurrentIrql() > APC_LEVEL) return;
if (InterlockedCompareExchange(&g_TerminationStopping, 0, 0) != 0) return;
PEPROCESS Process = NULL;
NTSTATUS Status = PsLookupProcessByProcessId(ProcessId, &Process);
if (!NT_SUCCESS(Status)) return;
PRULE_TERMINATION_WORK_ITEM Work = (PRULE_TERMINATION_WORK_ITEM)PyasAllocate(sizeof(RULE_TERMINATION_WORK_ITEM));
if (!Work) {
ObDereferenceObject(Process);
return;
}
RtlZeroMemory(Work, sizeof(*Work));
Work->Process = Process;
LONG Pending = InterlockedIncrement(&g_PendingTerminations);
if (Pending > MAX_PENDING_TERMINATIONS) {
ReleaseRuleTerminationWork(Work);
return;
}
KIRQL OldIrql;
KeAcquireSpinLock(&g_TerminationLock, &OldIrql);
if (InterlockedCompareExchange(&g_TerminationStopping, 0, 0) != 0) {
KeReleaseSpinLock(&g_TerminationLock, OldIrql);
ReleaseRuleTerminationWork(Work);
return;
}
InsertTailList(&g_TerminationQueue, &Work->Entry);
KeReleaseSpinLock(&g_TerminationLock, OldIrql);
KeReleaseSemaphore(&g_TerminationSemaphore, IO_NO_INCREMENT, 1, FALSE);
}
static VOID FreeList(PRULE_NODE* Head) {
PRULE_NODE Current = *Head;
while (Current) {
PRULE_NODE Next = Current->Next;
if (Current->Pattern.Buffer) PyasFree(Current->Pattern.Buffer);
PyasFree(Current);
Current = Next;
}
*Head = NULL;
}
static VOID FreeDynamicRule(PDYNAMIC_RULE Rule) {
if (!Rule) return;
FreeList(&Rule->Initiator);
FreeList(&Rule->InitiatorExclude);
FreeList(&Rule->InitiatorParent);
FreeList(&Rule->InitiatorParentExclude);
FreeList(&Rule->InitiatorProcessTree);
FreeList(&Rule->InitiatorProcessTreeExclude);
FreeList(&Rule->Target);
FreeList(&Rule->TargetExclude);
FreeList(&Rule->TargetProcessTree);
FreeList(&Rule->TargetProcessTreeExclude);
FreeList(&Rule->Creator);
FreeList(&Rule->CreatorExclude);
FreeList(&Rule->Parent);
FreeList(&Rule->ParentExclude);
FreeList(&Rule->CommandLine);
FreeList(&Rule->CommandLineExclude);
FreeList(&Rule->Extensions);
PyasFree(Rule);
}
static VOID AddRule(PRULE_NODE* Head, PUNICODE_STRING RuleStr) {
if (!RuleStr || !RuleStr->Buffer) return;
PRULE_NODE Check = *Head;
while (Check) {
if (RtlEqualUnicodeString(&Check->Pattern, RuleStr, TRUE)) return;
Check = Check->Next;
}
PRULE_NODE Node = (PRULE_NODE)PyasAllocate(sizeof(RULE_NODE));
if (!Node) return;
SIZE_T Size = RuleStr->Length + sizeof(WCHAR);
Node->Pattern.Buffer = (PWCHAR)PyasAllocate(Size);
if (!Node->Pattern.Buffer) {
PyasFree(Node);
return;
}
RtlCopyMemory(Node->Pattern.Buffer, RuleStr->Buffer, RuleStr->Length);
Node->Pattern.Buffer[RuleStr->Length / sizeof(WCHAR)] = L'\0';
Node->Pattern.Length = RuleStr->Length;
Node->Pattern.MaximumLength = (USHORT)Size;
Node->Next = *Head;
*Head = Node;
}
static VOID RemoveRule(PRULE_NODE* Head, PUNICODE_STRING RuleStr) {
if (!RuleStr || !RuleStr->Buffer) return;
PRULE_NODE Current = *Head;
PRULE_NODE Previous = NULL;
while (Current) {
if (RtlEqualUnicodeString(&Current->Pattern, RuleStr, TRUE)) {
PRULE_NODE ToDelete = Current;
if (Previous) Previous->Next = Current->Next;
else *Head = Current->Next;
Current = Current->Next;
if (ToDelete->Pattern.Buffer) PyasFree(ToDelete->Pattern.Buffer);
PyasFree(ToDelete);
}
else {
Previous = Current;
Current = Current->Next;
}
}
}
static BOOLEAN HasSuffix(PCUNICODE_STRING String, PCWSTR Suffix) {
if (!String || !String->Buffer || !Suffix) return FALSE;
SIZE_T StringLenChars = String->Length / sizeof(WCHAR);
SIZE_T SuffixLenChars = wcslen(Suffix);
if (StringLenChars < SuffixLenChars) return FALSE;
PCWSTR Ptr = String->Buffer + (StringLenChars - SuffixLenChars);
for (SIZE_T i = 0; i < SuffixLenChars; i++) {
if (RtlDowncaseUnicodeChar(Ptr[i]) != RtlDowncaseUnicodeChar(Suffix[i])) return FALSE;
}
return TRUE;
}
BOOLEAN WildcardMatch(PCWSTR Pattern, PCWSTR String, USHORT StringLengthBytes) {
if (Pattern == NULL || String == NULL) return FALSE;
USHORT StringLenChars = StringLengthBytes / sizeof(WCHAR);
PCWSTR mp = NULL;
PCWSTR cp = NULL;
PCWSTR StringEnd = String + StringLenChars;
while (String < StringEnd) {
if (*Pattern == L'*') {
mp = ++Pattern;
cp = String + 1;
}
else if (*Pattern == L'?' || (RtlDowncaseUnicodeChar(*Pattern) == RtlDowncaseUnicodeChar(*String))) {
Pattern++;
String++;
}
else if (mp != NULL) {
Pattern = mp;
String = cp++;
}
else {
return FALSE;
}
}
while (*Pattern == L'*') Pattern++;
return (*Pattern == L'\0') ? TRUE : FALSE;
}
static BOOLEAN MatchNodeListAny(PRULE_NODE Node, PCUNICODE_STRING Target) {
if (!Node) return FALSE;
if (!Target || !Target->Buffer) return FALSE;
while (Node) {
if (WildcardMatch(Node->Pattern.Buffer, Target->Buffer, Target->Length)) return TRUE;
Node = Node->Next;
}
return FALSE;
}
static ULONG GetPatternSpecificity(PCUNICODE_STRING Pattern) {
if (!Pattern || !Pattern->Buffer) return 0;
ULONG Score = 1;
ULONG Length = Pattern->Length / sizeof(WCHAR);
for (ULONG i = 0; i < Length; i++) {
if (Pattern->Buffer[i] != L'*' && Pattern->Buffer[i] != L'?') {
Score++;
}
}
return Score;
}
static ULONG GetNodeMatchSpecificity(PRULE_NODE Node, PCUNICODE_STRING Target) {
if (!Node || !Target || !Target->Buffer) return 0;
ULONG MaxScore = 0;
while (Node) {
if (WildcardMatch(Node->Pattern.Buffer, Target->Buffer, Target->Length)) {
ULONG Score = GetPatternSpecificity(&Node->Pattern);
if (Score > MaxScore) MaxScore = Score;
}
Node = Node->Next;
}
return MaxScore;
}
static BOOLEAN EvaluateNodeConflict(PRULE_NODE IncludeNode, PRULE_NODE ExcludeNode, PCUNICODE_STRING TargetString) {
if (!TargetString) {
if (IncludeNode) return FALSE;
return TRUE;
}
ULONG IncludeScore = 0;
ULONG ExcludeScore = 0;
BOOLEAN HasInclude = (IncludeNode != NULL);
BOOLEAN HasExclude = (ExcludeNode != NULL);
if (HasInclude) {
IncludeScore = GetNodeMatchSpecificity(IncludeNode, TargetString);
if (IncludeScore == 0) return FALSE;
}
if (HasExclude) {
ExcludeScore = GetNodeMatchSpecificity(ExcludeNode, TargetString);
if (ExcludeScore > 0 && ExcludeScore >= (HasInclude ? IncludeScore : 1)) {
return FALSE;
}
}
return TRUE;
}
static BOOLEAN MatchExtensionList(PRULE_NODE Node, PCUNICODE_STRING Target) {
if (!Node) return FALSE;
if (!Target || !Target->Buffer) return FALSE;
while (Node) {
if (HasSuffix(Target, Node->Pattern.Buffer)) return TRUE;
Node = Node->Next;
}
return FALSE;
}
static ULONG ProcessJsonUnescape(PWCHAR Buffer, ULONG LengthChars) {
if (!Buffer || LengthChars == 0) return 0;
ULONG WriteIdx = 0, ReadIdx = 0;
while (ReadIdx < LengthChars) {
if (Buffer[ReadIdx] == L'\\' && (ReadIdx + 1 < LengthChars)) {
WCHAR NextChar = Buffer[ReadIdx + 1];
if (NextChar == L'\\' || NextChar == L'"' || NextChar == L'/') {
Buffer[WriteIdx++] = NextChar; ReadIdx += 2;
}
else if (NextChar == L'n') { Buffer[WriteIdx++] = L'\n'; ReadIdx += 2; }
else if (NextChar == L'r') { Buffer[WriteIdx++] = L'\r'; ReadIdx += 2; }
else if (NextChar == L't') { Buffer[WriteIdx++] = L'\t'; ReadIdx += 2; }
else { Buffer[WriteIdx++] = Buffer[ReadIdx++]; }
}
else { Buffer[WriteIdx++] = Buffer[ReadIdx++]; }
}
Buffer[WriteIdx] = L'\0';
return WriteIdx * sizeof(WCHAR);
}
static VOID SkipWhitespace(PCHAR* Ptr, PCHAR End) {
while (*Ptr < End) {
char c = **Ptr;
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') (*Ptr)++;
else break;
}
}
static ULONGLONG ParseUnsigned64(PCHAR* Ptr, PCHAR End) {
ULONGLONG Value = 0;
ULONG Base = 10;
SkipWhitespace(Ptr, End);
if (*Ptr + 2 <= End && (*Ptr)[0] == '0' && ((*Ptr)[1] == 'x' || (*Ptr)[1] == 'X')) {
Base = 16;
*Ptr += 2;
}
while (*Ptr < End) {
CHAR Character = **Ptr;
ULONG Digit = 0;
if (Character >= '0' && Character <= '9') Digit = Character - '0';
else if (Base == 16 && Character >= 'a' && Character <= 'f') Digit = Character - 'a' + 10;
else if (Base == 16 && Character >= 'A' && Character <= 'F') Digit = Character - 'A' + 10;
else break;
if (Digit >= Base) break;
Value = Value * Base + Digit;
(*Ptr)++;
}
return Value;
}
static ULONG ParseInt(PCHAR* Ptr, PCHAR End) {
ULONGLONG Value = ParseUnsigned64(Ptr, End);
return Value > MAXULONG ? MAXULONG : (ULONG)Value;
}
static BOOLEAN ParseBoolean(PCHAR* Ptr, PCHAR End, PBOOLEAN Value) {
SkipWhitespace(Ptr, End);
if (!Value) return FALSE;
if (*Ptr + 4 <= End && RtlCompareMemory(*Ptr, "true", 4) == 4) {
*Value = TRUE;
*Ptr += 4;
return TRUE;
}
if (*Ptr + 5 <= End && RtlCompareMemory(*Ptr, "false", 5) == 5) {
*Value = FALSE;
*Ptr += 5;
return TRUE;
}
return FALSE;
}
static BOOLEAN ParseStringValue(PCHAR* Ptr, PCHAR End, PCHAR Buffer, ULONG BufferSize) {
SkipWhitespace(Ptr, End);
if (!Buffer || BufferSize == 0 || *Ptr >= End || **Ptr != '"') return FALSE;
(*Ptr)++;
ULONG Length = 0;
BOOLEAN Escaped = FALSE;
while (*Ptr < End) {
CHAR Character = **Ptr;
if (Character == '"' && !Escaped) break;
if (Length + 1 < BufferSize) {
Buffer[Length++] = Character;
}
if (Character == '\\' && !Escaped) Escaped = TRUE;
else Escaped = FALSE;
(*Ptr)++;
}
if (*Ptr >= End || **Ptr != '"') return FALSE;
(*Ptr)++;
Buffer[Length] = '\0';
return TRUE;
}
static VOID SkipJsonValue(PCHAR* Ptr, PCHAR End) {
SkipWhitespace(Ptr, End);
if (*Ptr >= End) return;
if (**Ptr == '"') {
CHAR Buffer[2] = { 0 };
ParseStringValue(Ptr, End, Buffer, sizeof(Buffer));
return;
}
if (**Ptr == '[' || **Ptr == '{') {
CHAR Open = **Ptr;
CHAR Close = Open == '[' ? ']' : '}';
LONG Depth = 0;
BOOLEAN InString = FALSE;
BOOLEAN Escaped = FALSE;
while (*Ptr < End) {
CHAR Character = **Ptr;
if (InString) {
if (Character == '"' && !Escaped) InString = FALSE;
if (Character == '\\' && !Escaped) Escaped = TRUE;
else Escaped = FALSE;
(*Ptr)++;
continue;
}
if (Character == '"') InString = TRUE;
else if (Character == Open) Depth++;
else if (Character == Close) {
Depth--;
(*Ptr)++;
if (Depth == 0) return;
continue;
}
(*Ptr)++;
}
return;
}
while (*Ptr < End && **Ptr != ',' && **Ptr != '}') {
(*Ptr)++;
}
}
static VOID ParseStringArray(PCHAR* Ptr, PCHAR End, PRULE_NODE* Head) {
SkipWhitespace(Ptr, End);
if (*Ptr >= End || **Ptr != '[') return;
(*Ptr)++;
while (*Ptr < End) {
SkipWhitespace(Ptr, End);
if (*Ptr >= End || **Ptr == ']') {
if (*Ptr < End) (*Ptr)++;
return;
}
if (**Ptr != '"') {
(*Ptr)++;
continue;
}
PCHAR StartQuote = ++(*Ptr);
BOOLEAN Escaped = FALSE;
while (*Ptr < End) {
CHAR Character = **Ptr;
if (Character == '"' && !Escaped) break;
if (Character == '\\' && !Escaped) Escaped = TRUE;
else Escaped = FALSE;
(*Ptr)++;
}
if (*Ptr >= End || **Ptr != '"') return;
ULONG Utf8Length = (ULONG)(*Ptr - StartQuote);
if (Utf8Length > 0) {
ULONG WideSize = 0;
RtlUTF8ToUnicodeN(NULL, 0, &WideSize, StartQuote, Utf8Length);
if (WideSize > 0 && WideSize <= 0xFFFC) {
PWCHAR WideBuffer = (PWCHAR)PyasAllocate(WideSize + sizeof(WCHAR));
if (WideBuffer) {
ULONG ResultSize = 0;
RtlZeroMemory(WideBuffer, WideSize + sizeof(WCHAR));
RtlUTF8ToUnicodeN(WideBuffer, WideSize, &ResultSize, StartQuote, Utf8Length);
ULONG FinalSize = ProcessJsonUnescape(WideBuffer, ResultSize / sizeof(WCHAR));
UNICODE_STRING Value;
Value.Buffer = WideBuffer;
Value.Length = (USHORT)FinalSize;
Value.MaximumLength = (USHORT)(WideSize + sizeof(WCHAR));
AddRule(Head, &Value);
PyasFree(WideBuffer);
}
}
}
(*Ptr)++;
}
}
static BOOLEAN RuleNodeEquals(PRULE_NODE Node, PCWSTR Value) {
UNICODE_STRING Target;
RtlInitUnicodeString(&Target, Value);
return Node && RtlEqualUnicodeString(&Node->Pattern, &Target, TRUE);
}
static BOOLEAN ParseHandleTypesArray(PCHAR* Ptr, PCHAR End, PULONG HandleTypes) {
PRULE_NODE Values = NULL;
ParseStringArray(Ptr, End, &Values);
BOOLEAN Valid = Values != NULL;
for (PRULE_NODE Node = Values; Node; Node = Node->Next) {
if (RuleNodeEquals(Node, L"Create")) *HandleTypes |= PYAS_HANDLE_CREATE;
else if (RuleNodeEquals(Node, L"Duplicate")) *HandleTypes |= PYAS_HANDLE_DUPLICATE;
else Valid = FALSE;
}
FreeList(&Values);
return Valid;
}
static BOOLEAN ParseObjectTypesArray(PCHAR* Ptr, PCHAR End, PULONG ObjectTypes) {
PRULE_NODE Values = NULL;
ParseStringArray(Ptr, End, &Values);
BOOLEAN Valid = Values != NULL;
for (PRULE_NODE Node = Values; Node; Node = Node->Next) {
if (RuleNodeEquals(Node, L"Process")) *ObjectTypes |= PYAS_OBJECT_PROCESS;
else if (RuleNodeEquals(Node, L"Thread")) *ObjectTypes |= PYAS_OBJECT_THREAD;
else Valid = FALSE;
}
FreeList(&Values);
return Valid;
}
static BOOLEAN ParseThreadMemoryTypesArray(PCHAR* Ptr, PCHAR End, PULONG MemoryTypes) {
PRULE_NODE Values = NULL;
ParseStringArray(Ptr, End, &Values);
BOOLEAN Valid = Values != NULL;
for (PRULE_NODE Node = Values; Node; Node = Node->Next) {
if (RuleNodeEquals(Node, L"Private")) *MemoryTypes |= PYAS_MEMORY_PRIVATE;
else if (RuleNodeEquals(Node, L"Mapped")) *MemoryTypes |= PYAS_MEMORY_MAPPED;
else if (RuleNodeEquals(Node, L"Image")) *MemoryTypes |= PYAS_MEMORY_IMAGE;
else Valid = FALSE;
}
FreeList(&Values);
return Valid;
}
static BOOLEAN ParseThreadMemoryProtectionsArray(PCHAR* Ptr, PCHAR End, PULONG Protections) {
PRULE_NODE Values = NULL;
ParseStringArray(Ptr, End, &Values);
BOOLEAN Valid = Values != NULL;
for (PRULE_NODE Node = Values; Node; Node = Node->Next) {
if (RuleNodeEquals(Node, L"Execute")) *Protections |= PYAS_PROTECT_EXECUTE;
else if (RuleNodeEquals(Node, L"ExecuteWrite")) *Protections |= PYAS_PROTECT_EXECUTE_WRITE;
else Valid = FALSE;
}
FreeList(&Values);
return Valid;
}
static RULE_TRI_STATE ParseTriState(PCHAR* Ptr, PCHAR End, PBOOLEAN Parsed) {
if (Parsed) *Parsed = FALSE;
BOOLEAN Value = FALSE;
if (!ParseBoolean(Ptr, End, &Value)) return RuleTriAny;
if (Parsed) *Parsed = TRUE;
return Value ? RuleTriTrue : RuleTriFalse;
}
static RULE_OPERATION_MATCH ParseOperationMatch(PCHAR* Ptr, PCHAR End, PBOOLEAN Parsed) {
if (Parsed) *Parsed = FALSE;
CHAR Value[16] = { 0 };
if (!ParseStringValue(Ptr, End, Value, sizeof(Value))) return RuleOperationAny;
if (strcmp(Value, "Any") == 0) {
if (Parsed) *Parsed = TRUE;
return RuleOperationAny;
}
if (strcmp(Value, "All") == 0) {
if (Parsed) *Parsed = TRUE;
return RuleOperationAll;
}
return RuleOperationAny;
}
static BOOLEAN ParseOperationsArray(PCHAR* Ptr, PCHAR End, PULONG Operations) {
PRULE_NODE Values = NULL;
ParseStringArray(Ptr, End, &Values);
BOOLEAN Valid = Values != NULL;
for (PRULE_NODE Node = Values; Node; Node = Node->Next) {
if (RuleNodeEquals(Node, L"Write")) *Operations |= OP_WRITE;
else if (RuleNodeEquals(Node, L"Delete")) *Operations |= OP_DELETE;
else if (RuleNodeEquals(Node, L"Create")) *Operations |= OP_CREATE;
else if (RuleNodeEquals(Node, L"Execute")) *Operations |= OP_EXECUTE;
else if (RuleNodeEquals(Node, L"Rename")) *Operations |= OP_RENAME;
else if (RuleNodeEquals(Node, L"Ioctl")) *Operations |= OP_IOCTL;
else if (RuleNodeEquals(Node, L"VmRead")) *Operations |= OP_VM_READ;
else if (RuleNodeEquals(Node, L"VmWrite")) {
*Operations |= OP_VM_WRITE | OP_VM_OPERATION | OP_CREATE_THREAD | OP_THREAD_SET_CONTEXT;
}
else if (RuleNodeEquals(Node, L"WriteMemory")) *Operations |= OP_VM_WRITE;
else if (RuleNodeEquals(Node, L"VmOperation")) *Operations |= OP_VM_OPERATION;
else if (RuleNodeEquals(Node, L"CreateThread") || RuleNodeEquals(Node, L"CreateRemoteThread")) {
*Operations |= OP_CREATE_THREAD;
}
else if (RuleNodeEquals(Node, L"SetThreadContext")) *Operations |= OP_THREAD_SET_CONTEXT;
else if (RuleNodeEquals(Node, L"SetThreadToken")) *Operations |= OP_THREAD_SET_TOKEN;
else if (RuleNodeEquals(Node, L"Terminate")) *Operations |= OP_TERMINATE;
else if (RuleNodeEquals(Node, L"SuspendResume")) *Operations |= OP_SUSPEND_RESUME;
else if (RuleNodeEquals(Node, L"DuplicateHandle")) *Operations |= OP_DUP_HANDLE;
else if (RuleNodeEquals(Node, L"SetInformation")) *Operations |= OP_SET_INFORMATION;
else if (RuleNodeEquals(Node, L"CreateProcess")) *Operations |= OP_CREATE_PROCESS;
else if (RuleNodeEquals(Node, L"ImageLoad")) *Operations |= OP_IMAGE_LOAD;
else if (RuleNodeEquals(Node, L"Impersonate")) *Operations |= OP_IMPERSONATE;
else Valid = FALSE;
}
FreeList(&Values);
return Valid;
}
static BOOLEAN KeyEquals(PCHAR Key, ULONG KeyLength, PCSTR Expected) {
SIZE_T ExpectedLength = strlen(Expected);
return KeyLength == ExpectedLength && RtlCompareMemory(Key, Expected, ExpectedLength) == ExpectedLength;
}
static RULE_CATEGORY ParseCategoryValue(PCHAR* Ptr, PCHAR End) {
CHAR Value[24] = { 0 };
if (!ParseStringValue(Ptr, End, Value, sizeof(Value))) return RuleCategoryUnknown;
if (strcmp(Value, "Process") == 0) return RuleCategoryProcess;
if (strcmp(Value, "File") == 0) return RuleCategoryFile;
if (strcmp(Value, "Registry") == 0) return RuleCategoryRegistry;
if (strcmp(Value, "Device") == 0) return RuleCategoryDevice;
if (strcmp(Value, "Memory") == 0) return RuleCategoryMemory;
if (strcmp(Value, "Thread") == 0) return RuleCategoryThread;
return RuleCategoryUnknown;
}
static BOOLEAN IsValidDynamicRule(PDYNAMIC_RULE Rule) {
if (!Rule || Rule->Invalid) return FALSE;
if (Rule->Code == 0 || Rule->Category == RuleCategoryUnknown || Rule->Operations == 0) return FALSE;
ULONG AllowedOperations = 0;
if (Rule->Category == RuleCategoryProcess) {
AllowedOperations =
OP_EXECUTE |
OP_TERMINATE |
OP_SUSPEND_RESUME |
OP_DUP_HANDLE |
OP_SET_INFORMATION |
OP_THREAD_SET_TOKEN |
OP_CREATE_PROCESS |
OP_IMAGE_LOAD |
OP_IMPERSONATE;
}
else if (Rule->Category == RuleCategoryMemory) {
AllowedOperations =
OP_VM_READ |
OP_VM_WRITE |
OP_VM_OPERATION |
OP_CREATE_THREAD |
OP_THREAD_SET_CONTEXT;
}
else if (Rule->Category == RuleCategoryThread) {
AllowedOperations = OP_EXECUTE;
}
else if (Rule->Category == RuleCategoryFile) {
AllowedOperations = OP_WRITE | OP_DELETE | OP_CREATE | OP_EXECUTE | OP_RENAME;
}
else if (Rule->Category == RuleCategoryRegistry) {
AllowedOperations = OP_WRITE | OP_DELETE | OP_CREATE;
}
else if (Rule->Category == RuleCategoryDevice) {
AllowedOperations = OP_IOCTL;
}
if ((Rule->Operations & ~AllowedOperations) != 0) return FALSE;
if (Rule->Threshold > 0 && Rule->TimeWindow == 0) Rule->TimeWindow = 1000;
if (Rule->MaximumRiskScore > 0 && Rule->MaximumRiskScore < Rule->MinimumRiskScore) return FALSE;
if (Rule->MaximumRegionSize > 0 && Rule->MaximumRegionSize < Rule->MinimumRegionSize) return FALSE;
return TRUE;
}
static VOID ParseDynamicRules(PCHAR JsonContent, ULONG ContentLength) {
if (!JsonContent || ContentLength == 0) return;
PCHAR Ptr = JsonContent;
PCHAR End = JsonContent + ContentLength;
const CHAR DynamicRulesKey[] = "\"DynamicRules\"";
SIZE_T DynamicRulesKeyLength = sizeof(DynamicRulesKey) - 1;
while (Ptr + DynamicRulesKeyLength <= End) {
if (RtlCompareMemory(Ptr, DynamicRulesKey, DynamicRulesKeyLength) != DynamicRulesKeyLength) {
Ptr++;
continue;
}
Ptr += DynamicRulesKeyLength;
SkipWhitespace(&Ptr, End);
if (Ptr >= End || *Ptr != ':') return;
Ptr++;
SkipWhitespace(&Ptr, End);
if (Ptr >= End || *Ptr != '[') return;
Ptr++;
while (Ptr < End) {
SkipWhitespace(&Ptr, End);
if (Ptr >= End || *Ptr == ']') return;
if (*Ptr != '{') {
Ptr++;
continue;
}
Ptr++;
PDYNAMIC_RULE Rule = (PDYNAMIC_RULE)PyasAllocate(sizeof(DYNAMIC_RULE));
if (!Rule) {
SkipJsonValue(&Ptr, End);
continue;
}
RtlZeroMemory(Rule, sizeof(*Rule));
Rule->Category = RuleCategoryUnknown;
Rule->OperationMatch = RuleOperationAny;
while (Ptr < End) {
SkipWhitespace(&Ptr, End);
if (Ptr >= End) break;
if (*Ptr == '}') {
Ptr++;
break;
}
if (*Ptr != '"') {
Ptr++;
continue;
}
PCHAR KeyStart = ++Ptr;
while (Ptr < End && *Ptr != '"') Ptr++;
if (Ptr >= End) break;
ULONG KeyLength = (ULONG)(Ptr - KeyStart);
Ptr++;
SkipWhitespace(&Ptr, End);
if (Ptr >= End || *Ptr != ':') {
SkipJsonValue(&Ptr, End);
continue;
}
Ptr++;
SkipWhitespace(&Ptr, End);
if (KeyEquals(KeyStart, KeyLength, "Code")) Rule->Code = ParseInt(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "Kill")) {
if (!ParseBoolean(&Ptr, End, &Rule->Kill)) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "Priority")) Rule->Priority = ParseInt(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "Threshold")) Rule->Threshold = ParseInt(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "TimeWindow")) Rule->TimeWindow = ParseInt(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "MinimumRiskScore")) Rule->MinimumRiskScore = ParseInt(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "MaximumRiskScore")) Rule->MaximumRiskScore = ParseInt(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "MinimumRegionSize")) Rule->MinimumRegionSize = ParseUnsigned64(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "MaximumRegionSize")) Rule->MaximumRegionSize = ParseUnsigned64(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "Category")) Rule->Category = ParseCategoryValue(&Ptr, End);
else if (KeyEquals(KeyStart, KeyLength, "OperationMatch")) {
BOOLEAN Parsed = FALSE;
Rule->OperationMatch = ParseOperationMatch(&Ptr, End, &Parsed);
if (!Parsed) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "ParentMismatch")) {
BOOLEAN Parsed = FALSE;
Rule->ParentMismatch = ParseTriState(&Ptr, End, &Parsed);
if (!Parsed) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "FileOpenNameAvailable")) {
BOOLEAN Parsed = FALSE;
Rule->FileOpenNameAvailable = ParseTriState(&Ptr, End, &Parsed);
if (!Parsed) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "SubsystemProcess")) {
BOOLEAN Parsed = FALSE;
Rule->SubsystemProcess = ParseTriState(&Ptr, End, &Parsed);
if (!Parsed) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "Initiator")) ParseStringArray(&Ptr, End, &Rule->Initiator);
else if (KeyEquals(KeyStart, KeyLength, "InitiatorExclude")) ParseStringArray(&Ptr, End, &Rule->InitiatorExclude);
else if (KeyEquals(KeyStart, KeyLength, "InitiatorParent")) ParseStringArray(&Ptr, End, &Rule->InitiatorParent);
else if (KeyEquals(KeyStart, KeyLength, "InitiatorParentExclude")) ParseStringArray(&Ptr, End, &Rule->InitiatorParentExclude);
else if (KeyEquals(KeyStart, KeyLength, "InitiatorProcessTree")) ParseStringArray(&Ptr, End, &Rule->InitiatorProcessTree);
else if (KeyEquals(KeyStart, KeyLength, "InitiatorProcessTreeExclude")) ParseStringArray(&Ptr, End, &Rule->InitiatorProcessTreeExclude);
else if (KeyEquals(KeyStart, KeyLength, "Target")) ParseStringArray(&Ptr, End, &Rule->Target);
else if (KeyEquals(KeyStart, KeyLength, "TargetExclude")) ParseStringArray(&Ptr, End, &Rule->TargetExclude);
else if (KeyEquals(KeyStart, KeyLength, "TargetProcessTree")) ParseStringArray(&Ptr, End, &Rule->TargetProcessTree);
else if (KeyEquals(KeyStart, KeyLength, "TargetProcessTreeExclude")) ParseStringArray(&Ptr, End, &Rule->TargetProcessTreeExclude);
else if (KeyEquals(KeyStart, KeyLength, "Creator")) ParseStringArray(&Ptr, End, &Rule->Creator);
else if (KeyEquals(KeyStart, KeyLength, "CreatorExclude")) ParseStringArray(&Ptr, End, &Rule->CreatorExclude);
else if (KeyEquals(KeyStart, KeyLength, "Parent")) ParseStringArray(&Ptr, End, &Rule->Parent);
else if (KeyEquals(KeyStart, KeyLength, "ParentExclude")) ParseStringArray(&Ptr, End, &Rule->ParentExclude);
else if (KeyEquals(KeyStart, KeyLength, "CommandLine")) ParseStringArray(&Ptr, End, &Rule->CommandLine);
else if (KeyEquals(KeyStart, KeyLength, "CommandLineExclude")) ParseStringArray(&Ptr, End, &Rule->CommandLineExclude);
else if (KeyEquals(KeyStart, KeyLength, "Extensions")) ParseStringArray(&Ptr, End, &Rule->Extensions);
else if (KeyEquals(KeyStart, KeyLength, "Operations")) {
if (!ParseOperationsArray(&Ptr, End, &Rule->Operations)) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "HandleTypes")) {
if (!ParseHandleTypesArray(&Ptr, End, &Rule->HandleTypes)) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "ObjectTypes")) {
if (!ParseObjectTypesArray(&Ptr, End, &Rule->ObjectTypes)) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "ThreadMemoryTypes")) {
if (!ParseThreadMemoryTypesArray(&Ptr, End, &Rule->ThreadMemoryTypes)) Rule->Invalid = TRUE;
}
else if (KeyEquals(KeyStart, KeyLength, "ThreadMemoryProtections")) {
if (!ParseThreadMemoryProtectionsArray(&Ptr, End, &Rule->ThreadMemoryProtections)) Rule->Invalid = TRUE;
}
else SkipJsonValue(&Ptr, End);
}
if (IsValidDynamicRule(Rule)) {
Rule->Next = g_DynamicRules;
g_DynamicRules = Rule;
}
else {
FreeDynamicRule(Rule);
}
}
return;
}
}
NTSTATUS InitializeRulesEngine() {
if (g_CacheInitialized) return STATUS_SUCCESS;
NTSTATUS Status = ExInitializeResourceLite(&RuleLock);
if (!NT_SUCCESS(Status)) return Status;
KeInitializeSpinLock(&TrustCacheLock);
InitializeListHead(&g_TerminationQueue);
KeInitializeSpinLock(&g_TerminationLock);
KeInitializeSemaphore(&g_TerminationSemaphore, 0, MAXLONG);
InterlockedExchange(&g_PendingTerminations, 0);
InterlockedExchange(&g_TerminationStopping, 0);
Status = StartRuleTerminationWorker();
if (!NT_SUCCESS(Status)) {
InterlockedExchange(&g_TerminationStopping, 1);
ExDeleteResourceLite(&RuleLock);
return Status;
}
RtlZeroMemory(TrustCache, sizeof(TrustCache));
RtlZeroMemory(BehaviorTrackers, sizeof(BehaviorTrackers));
g_CacheInitialized = TRUE;
return STATUS_SUCCESS;
}
VOID UninitializeRulesEngine() {
if (!g_CacheInitialized) return;
InterlockedExchange(&g_TerminationStopping, 1);
StopRuleTerminationWorker();
g_CacheInitialized = FALSE;
ExDeleteResourceLite(&RuleLock);
}
VOID AddDynamicWhitelist(PUNICODE_STRING RuleStr) {
KeEnterCriticalRegion();
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
AddRule(&g_ProcessTrustedPaths, RuleStr);
KIRQL OldIrql;
KeAcquireSpinLock(&TrustCacheLock, &OldIrql);
RtlZeroMemory(TrustCache, sizeof(TrustCache));
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
}
VOID RemoveDynamicWhitelist(PUNICODE_STRING RuleStr) {
KeEnterCriticalRegion();
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
RemoveRule(&g_ProcessTrustedPaths, RuleStr);
KIRQL OldIrql;
KeAcquireSpinLock(&TrustCacheLock, &OldIrql);
RtlZeroMemory(TrustCache, sizeof(TrustCache));
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
}
NTSTATUS LoadRulesFromDisk(PUNICODE_STRING RegistryPath) {
NTSTATUS status = STATUS_SUCCESS;
HANDLE FileHandle = NULL;
IO_STATUS_BLOCK IoStatus = { 0 };
OBJECT_ATTRIBUTES oa = { 0 };
UNICODE_STRING ImagePathName;
OBJECT_ATTRIBUTES RegOa = { 0 };
HANDLE RegHandle = NULL;
PKEY_VALUE_PARTIAL_INFORMATION Info = NULL;
ULONG ResultLength = 0;
PWCHAR PathBuffer = NULL;
SIZE_T PathBufferSize = 0;
UNICODE_STRING FinalPath = { 0 };
RtlInitUnicodeString(&ImagePathName, L"ImagePath");
InitializeObjectAttributes(&RegOa, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwOpenKey(&RegHandle, KEY_READ, &RegOa);
if (!NT_SUCCESS(status)) return status;
status = ZwQueryValueKey(RegHandle, &ImagePathName, KeyValuePartialInformation, NULL, 0, &ResultLength);
if (status != STATUS_BUFFER_TOO_SMALL) {
ZwClose(RegHandle);
return status;
}
Info = (PKEY_VALUE_PARTIAL_INFORMATION)PyasAllocate(ResultLength);
if (!Info) {
ZwClose(RegHandle);
return STATUS_INSUFFICIENT_RESOURCES;
}
status = ZwQueryValueKey(RegHandle, &ImagePathName, KeyValuePartialInformation, Info, ResultLength, &ResultLength);
ZwClose(RegHandle);
if (!NT_SUCCESS(status)) {
PyasFree(Info);
return status;
}
if (Info->Type == REG_EXPAND_SZ || Info->Type == REG_SZ) {
PathBufferSize = Info->DataLength + 1024;
PathBuffer = (PWCHAR)PyasAllocate(PathBufferSize);
if (PathBuffer) {
RtlZeroMemory(PathBuffer, PathBufferSize);
if (Info->DataLength > 0) RtlCopyMemory(PathBuffer, Info->Data, Info->DataLength);
PWCHAR LastSlash = NULL;
PWCHAR Current = PathBuffer;
while (*Current) {
if (*Current == L'\\') LastSlash = Current;
Current++;
}
if (LastSlash) {
*LastSlash = L'\0';
SIZE_T CurrentPathLen = wcslen(PathBuffer);
const WCHAR FilterSuffix[] = L"\\Filter";
SIZE_T FilterLen = (sizeof(FilterSuffix) / sizeof(WCHAR)) - 1;
if (CurrentPathLen >= FilterLen) {
PWCHAR SuffixStart = PathBuffer + CurrentPathLen - FilterLen;
BOOLEAN Match = TRUE;
for (SIZE_T i = 0; i < FilterLen; i++) {
if (RtlDowncaseUnicodeChar(SuffixStart[i]) != RtlDowncaseUnicodeChar(FilterSuffix[i])) {
Match = FALSE;
break;
}
}
if (Match) *SuffixStart = L'\0';
}
RtlStringCbCatW(PathBuffer, PathBufferSize, L"\\Rules\\Rules_Driver_P1.json");
if (wcsncmp(PathBuffer, L"\\??\\", 4) != 0 && wcsncmp(PathBuffer, L"\\SystemRoot", 11) != 0 && wcsncmp(PathBuffer, L"\\DosDevices\\", 12) != 0) {
PWCHAR TmpBuffer = (PWCHAR)PyasAllocate(PathBufferSize + 16);
if (TmpBuffer) {
RtlStringCbCopyW(TmpBuffer, PathBufferSize + 16, L"\\??\\");
RtlStringCbCatW(TmpBuffer, PathBufferSize + 16, PathBuffer);
PyasFree(PathBuffer);
PathBuffer = TmpBuffer;
}
}
RtlInitUnicodeString(&FinalPath, PathBuffer);
}
}
}
PyasFree(Info);
if (!FinalPath.Buffer) {
if (PathBuffer) PyasFree(PathBuffer);
return STATUS_UNSUCCESSFUL;
}
InitializeObjectAttributes(&oa, &FinalPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwCreateFile(&FileHandle, GENERIC_READ | SYNCHRONIZE, &oa, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (NT_SUCCESS(status)) {
FILE_STANDARD_INFORMATION FileInfo = { 0 };
status = ZwQueryInformationFile(FileHandle, &IoStatus, &FileInfo, sizeof(FileInfo), FileStandardInformation);
if (NT_SUCCESS(status) && FileInfo.EndOfFile.HighPart != 0) {
status = STATUS_FILE_TOO_LARGE;
}
else if (NT_SUCCESS(status) && FileInfo.EndOfFile.LowPart > MAX_RULE_FILE_SIZE) {
status = STATUS_FILE_TOO_LARGE;
}
else if (NT_SUCCESS(status) && FileInfo.EndOfFile.LowPart == 0) {
status = STATUS_END_OF_FILE;
}
else if (NT_SUCCESS(status)) {
ULONG FileSize = FileInfo.EndOfFile.LowPart;
PVOID FileBuffer = PyasAllocate((SIZE_T)FileSize + 1);
if (!FileBuffer) {
status = STATUS_INSUFFICIENT_RESOURCES;
}
else {
status = ZwReadFile(FileHandle, NULL, NULL, NULL, &IoStatus, FileBuffer, FileSize, NULL, NULL);
if (NT_SUCCESS(status) && IoStatus.Information != FileSize) {
status = STATUS_END_OF_FILE;
}
if (NT_SUCCESS(status)) {
((PCHAR)FileBuffer)[FileSize] = '\0';
KeEnterCriticalRegion();
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
PDYNAMIC_RULE PreviousHead = g_DynamicRules;
ParseDynamicRules((PCHAR)FileBuffer, FileSize);
if (g_DynamicRules == PreviousHead) status = STATUS_DATA_ERROR;
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
}
PyasFree(FileBuffer);
}
}
ZwClose(FileHandle);
}
if (PathBuffer) PyasFree(PathBuffer);
return status;
}
NTSTATUS LoadRuleFile(PCUNICODE_STRING FilePath) {
if (!FilePath || !FilePath->Buffer) return STATUS_INVALID_PARAMETER;
NTSTATUS status = STATUS_SUCCESS;
HANDLE FileHandle = NULL;
IO_STATUS_BLOCK IoStatus = { 0 };
OBJECT_ATTRIBUTES oa = { 0 };
InitializeObjectAttributes(&oa, (PUNICODE_STRING)FilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwCreateFile(&FileHandle, GENERIC_READ | SYNCHRONIZE, &oa, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (NT_SUCCESS(status)) {
FILE_STANDARD_INFORMATION FileInfo = { 0 };
status = ZwQueryInformationFile(FileHandle, &IoStatus, &FileInfo, sizeof(FileInfo), FileStandardInformation);
if (NT_SUCCESS(status) && FileInfo.EndOfFile.HighPart != 0) {
status = STATUS_FILE_TOO_LARGE;
}
else if (NT_SUCCESS(status) && FileInfo.EndOfFile.LowPart > MAX_RULE_FILE_SIZE) {
status = STATUS_FILE_TOO_LARGE;
}
else if (NT_SUCCESS(status) && FileInfo.EndOfFile.LowPart == 0) {
status = STATUS_END_OF_FILE;
}
else if (NT_SUCCESS(status)) {
ULONG FileSize = FileInfo.EndOfFile.LowPart;
PVOID FileBuffer = PyasAllocate((SIZE_T)FileSize + 1);
if (!FileBuffer) {
status = STATUS_INSUFFICIENT_RESOURCES;
}
else {
status = ZwReadFile(FileHandle, NULL, NULL, NULL, &IoStatus, FileBuffer, FileSize, NULL, NULL);
if (NT_SUCCESS(status) && IoStatus.Information != FileSize) {
status = STATUS_END_OF_FILE;
}
if (NT_SUCCESS(status)) {
((PCHAR)FileBuffer)[FileSize] = '\0';
KeEnterCriticalRegion();
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
PDYNAMIC_RULE PreviousHead = g_DynamicRules;
ParseDynamicRules((PCHAR)FileBuffer, FileSize);
if (g_DynamicRules == PreviousHead) status = STATUS_DATA_ERROR;
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
}
PyasFree(FileBuffer);
}
}
ZwClose(FileHandle);
}
return status;
}
VOID ClearDynamicRules() {
KeEnterCriticalRegion();
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
PDYNAMIC_RULE CurrentRule = g_DynamicRules;
while (CurrentRule) {
PDYNAMIC_RULE Next = CurrentRule->Next;
FreeDynamicRule(CurrentRule);
CurrentRule = Next;
}
g_DynamicRules = NULL;
KIRQL OldIrql;
KeAcquireSpinLock(&GlobalData.TrackerMutex, &OldIrql);
RtlZeroMemory(BehaviorTrackers, sizeof(BehaviorTrackers));
KeReleaseSpinLock(&GlobalData.TrackerMutex, OldIrql);
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
}
VOID UnloadRules() {
ClearDynamicRules();
KeEnterCriticalRegion();
ExAcquireResourceExclusiveLite(&RuleLock, TRUE);
FreeList(&g_ProcessTrustedPaths);
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
}
NTSTATUS GetProcessImageName(HANDLE ProcessId, PUNICODE_STRING* ImageName) {
if (KeGetCurrentIrql() != PASSIVE_LEVEL) return STATUS_UNSUCCESSFUL;
PEPROCESS Process = NULL;
*ImageName = NULL;
NTSTATUS status = PsLookupProcessByProcessId(ProcessId, &Process);
if (!NT_SUCCESS(status)) return status;
status = SeLocateProcessImageName(Process, ImageName);
ObDereferenceObject(Process);
return status;
}
BOOLEAN IsProcessTrusted(HANDLE ProcessId) {
if (ProcessId == (HANDLE)4) return TRUE;
PEPROCESS Process = NULL;
if (!NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &Process))) return FALSE;
BOOLEAN IsConnectedClient = FALSE;
KIRQL PortIrql;
KeAcquireSpinLock(&GlobalData.PortMutex, &PortIrql);
IsConnectedClient = GlobalData.PyasProcess == Process;
KeReleaseSpinLock(&GlobalData.PortMutex, PortIrql);
if (IsConnectedClient) {
ObDereferenceObject(Process);
return TRUE;
}
LARGE_INTEGER createTime;
createTime.QuadPart = PsGetProcessCreateTimeQuadPart(Process);
if (g_CacheInitialized) {
KIRQL OldIrql;
KeAcquireSpinLock(&TrustCacheLock, &OldIrql);
ULONG Hash = (((ULONG)(ULONG_PTR)ProcessId) >> 2) & (TRUST_CACHE_SIZE - 1);
if (TrustCache[Hash].Process == Process && TrustCache[Hash].ProcessCreateTime.QuadPart == createTime.QuadPart) {
LARGE_INTEGER Now;
KeQuerySystemTime(&Now);
if ((Now.QuadPart - TrustCache[Hash].CacheTime.QuadPart) < (TRUST_CACHE_TTL_SEC * 10000000LL)) {
BOOLEAN cachedResult = TrustCache[Hash].IsTrusted;
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
ObDereferenceObject(Process);
return cachedResult;
}
}
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
}
if (KeGetCurrentIrql() != PASSIVE_LEVEL) {
ObDereferenceObject(Process);
return FALSE;
}
PUNICODE_STRING imageFileName = NULL;
NTSTATUS status = SeLocateProcessImageName(Process, &imageFileName);
BOOLEAN isTrusted = FALSE;
if (NT_SUCCESS(status) && imageFileName && imageFileName->Buffer) {
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&RuleLock, TRUE);
PRULE_NODE Node = g_ProcessTrustedPaths;
while (Node) {
if (WildcardMatch(Node->Pattern.Buffer, imageFileName->Buffer, imageFileName->Length)) {
isTrusted = TRUE;
break;
}
Node = Node->Next;
}
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
}
if (g_CacheInitialized) {
KIRQL OldIrql;
KeAcquireSpinLock(&TrustCacheLock, &OldIrql);
ULONG Hash = (((ULONG)(ULONG_PTR)ProcessId) >> 2) & (TRUST_CACHE_SIZE - 1);
TrustCache[Hash].Process = Process;
TrustCache[Hash].ProcessCreateTime = createTime;
TrustCache[Hash].IsTrusted = isTrusted;
KeQuerySystemTime(&TrustCache[Hash].CacheTime);
KeReleaseSpinLock(&TrustCacheLock, OldIrql);
}
if (imageFileName) ExFreePool(imageFileName);
ObDereferenceObject(Process);
return isTrusted;
}
static BOOLEAN CheckRuleThreshold(PDYNAMIC_RULE Rule, HANDLE ProcessId) {
if (Rule->Threshold == 0) return TRUE;
PEPROCESS Process = NULL;
if (!NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &Process))) return FALSE;
LARGE_INTEGER Now;
KeQuerySystemTime(&Now);
LARGE_INTEGER createTime;
createTime.QuadPart = PsGetProcessCreateTimeQuadPart(Process);
BOOLEAN Triggered = FALSE;
KIRQL OldIrql;
KeAcquireSpinLock(&GlobalData.TrackerMutex, &OldIrql);
PBEHAVIOR_TRACKER Tracker = NULL;
PBEHAVIOR_TRACKER CandidateSlot = NULL;
PBEHAVIOR_TRACKER LruSlot = &BehaviorTrackers[0];
for (int i = 0; i < MAX_TRACKERS; i++) {
PBEHAVIOR_TRACKER Current = &BehaviorTrackers[i];
if (Current->LastActivityTime.QuadPart < LruSlot->LastActivityTime.QuadPart) LruSlot = Current;
if (Current->Process == Process && Current->Rule == Rule && Current->ProcessCreateTime.QuadPart == createTime.QuadPart) {
Tracker = Current;
break;
}
BOOLEAN IsExpired = FALSE;
if (Current->Process != NULL) {
LARGE_INTEGER Diff;
Diff.QuadPart = Now.QuadPart - Current->LastActivityTime.QuadPart;
if (Diff.QuadPart > (Rule->TimeWindow * 10000LL)) IsExpired = TRUE;
}
if ((Current->Process == NULL || IsExpired) && !CandidateSlot) CandidateSlot = Current;
}
if (!Tracker) {
Tracker = CandidateSlot ? CandidateSlot : LruSlot;
Tracker->Process = Process;
Tracker->Rule = Rule;
Tracker->ProcessCreateTime = createTime;
Tracker->ActivityCount = 0;
Tracker->LastActivityTime = Now;
}
else {
LARGE_INTEGER Diff;
Diff.QuadPart = Now.QuadPart - Tracker->LastActivityTime.QuadPart;
if (Diff.QuadPart > (Rule->TimeWindow * 10000LL)) Tracker->ActivityCount = 0;
Tracker->LastActivityTime = Now;
}
Tracker->ActivityCount++;
if (Tracker->ActivityCount >= Rule->Threshold) Triggered = TRUE;
KeReleaseSpinLock(&GlobalData.TrackerMutex, OldIrql);
ObDereferenceObject(Process);
return Triggered;
}
static BOOLEAN TriStateMatches(RULE_TRI_STATE Condition, BOOLEAN Value) {
if (Condition == RuleTriAny) return TRUE;
if (Condition == RuleTriTrue) return Value ? TRUE : FALSE;
return Value ? FALSE : TRUE;
}
static BOOLEAN RiskScoreMatches(PDYNAMIC_RULE Rule, ULONG RiskScore) {
if (Rule->MinimumRiskScore > 0 && RiskScore < Rule->MinimumRiskScore) return FALSE;
if (Rule->MaximumRiskScore > 0 && RiskScore > Rule->MaximumRiskScore) return FALSE;
return TRUE;
}
static BOOLEAN GetProcessPath(HANDLE ProcessId, PUNICODE_STRING* Path) {
if (Path) *Path = NULL;
if (!Path || !ProcessId || KeGetCurrentIrql() != PASSIVE_LEVEL) return FALSE;
return NT_SUCCESS(GetProcessImageName(ProcessId, Path)) && *Path && (*Path)->Buffer;
}
static BOOLEAN MatchDirectParent(
HANDLE ProcessId,
PRULE_NODE Include,
PRULE_NODE Exclude
) {
if (!Include && !Exclude) return TRUE;
HANDLE ParentProcessId = NULL;
if (!GetProcessRelation(ProcessId, &ParentProcessId, NULL) || !ParentProcessId) return FALSE;
PUNICODE_STRING ParentPath = NULL;
if (!GetProcessPath(ParentProcessId, &ParentPath)) return FALSE;
BOOLEAN Match = EvaluateNodeConflict(Include, Exclude, ParentPath);
ExFreePool(ParentPath);
return Match;
}
static BOOLEAN MatchProcessTree(
HANDLE ProcessId,
PRULE_NODE Include,
PRULE_NODE Exclude
) {
if (!Include && !Exclude) return TRUE;
if (!ProcessId) return FALSE;
HANDLE CurrentProcessId = ProcessId;
HANDLE Visited[12] = { 0 };
ULONG VisitedCount = 0;
BOOLEAN IncludeMatched = Include ? FALSE : TRUE;
for (ULONG Depth = 0; Depth < RTL_NUMBER_OF(Visited); Depth++) {
BOOLEAN Seen = FALSE;
for (ULONG Index = 0; Index < VisitedCount; Index++) {
if (Visited[Index] == CurrentProcessId) {
Seen = TRUE;
break;
}
}
if (Seen) break;
Visited[VisitedCount++] = CurrentProcessId;
PUNICODE_STRING CurrentPath = NULL;
if (GetProcessPath(CurrentProcessId, &CurrentPath)) {
if (Exclude && MatchNodeListAny(Exclude, CurrentPath)) {
ExFreePool(CurrentPath);
return FALSE;
}
if (Include && MatchNodeListAny(Include, CurrentPath)) {
IncludeMatched = TRUE;
}
ExFreePool(CurrentPath);
}
HANDLE ParentProcessId = NULL;
if (!GetProcessRelation(CurrentProcessId, &ParentProcessId, NULL)) break;
if (!ParentProcessId || ParentProcessId == CurrentProcessId) break;
CurrentProcessId = ParentProcessId;
}
return IncludeMatched;
}
static ULONG CalculateAccessRisk(ULONG Operations, ULONG HandleType, ULONG ObjectType) {
ULONG Score = 0;
if (Operations & OP_VM_READ) Score += 10;
if (Operations & OP_VM_WRITE) Score += 35;
if (Operations & OP_VM_OPERATION) Score += 20;
if (Operations & OP_CREATE_THREAD) Score += 45;
if (Operations & OP_THREAD_SET_CONTEXT) Score += 50;
if (Operations & OP_THREAD_SET_TOKEN) Score += 55;
if (Operations & OP_TERMINATE) Score += 40;
if (Operations & OP_SUSPEND_RESUME) Score += 25;
if (Operations & OP_DUP_HANDLE) Score += 25;
if (Operations & OP_SET_INFORMATION) Score += 20;
if (Operations & OP_CREATE_PROCESS) Score += 35;
if (Operations & OP_IMPERSONATE) Score += 55;
if (HandleType == PYAS_HANDLE_DUPLICATE) Score += 10;
if (ObjectType == PYAS_OBJECT_THREAD && (Operations & (OP_THREAD_SET_CONTEXT | OP_THREAD_SET_TOKEN | OP_IMPERSONATE))) {
Score += 10;
}
return Score > 100 ? 100 : Score;
}
static ULONG CalculateThreadRisk(ULONG MemoryType, ULONG MemoryProtection, SIZE_T RegionSize) {
ULONG Score = 20;
if (MemoryType & PYAS_MEMORY_PRIVATE) Score += 40;
else if (MemoryType & PYAS_MEMORY_MAPPED) Score += 20;
else if (MemoryType & PYAS_MEMORY_IMAGE) Score += 5;
if (MemoryProtection & PYAS_PROTECT_EXECUTE_WRITE) Score += 30;
else if (MemoryProtection & PYAS_PROTECT_EXECUTE) Score += 10;
if (RegionSize > 0 && RegionSize <= 1024 * 1024) Score += 5;
return Score > 100 ? 100 : Score;
}
static ULONG CalculateProcessCreateRisk(HANDLE CreatorPid, HANDLE ParentPid, BOOLEAN IsSubsystemProcess) {
ULONG Score = 0;
if (CreatorPid && ParentPid && CreatorPid != ParentPid) Score += 35;
if (IsSubsystemProcess) Score += 10;
return Score;
}
static ULONG GetMatchedOperations(PDYNAMIC_RULE Rule, ULONG RequestedOperations) {
ULONG RuleOperations = Rule->Operations & RequestedOperations;
if (RuleOperations == 0) return 0;
if (Rule->OperationMatch == RuleOperationAll) {
ULONG RelevantOperations = Rule->Operations & (
OP_VM_READ |
OP_VM_WRITE |
OP_VM_OPERATION |
OP_CREATE_THREAD |
OP_THREAD_SET_CONTEXT |
OP_THREAD_SET_TOKEN |
OP_TERMINATE |
OP_SUSPEND_RESUME |
OP_DUP_HANDLE |
OP_SET_INFORMATION |
OP_CREATE_PROCESS |
OP_IMPERSONATE
);
if (RelevantOperations == 0 || (RequestedOperations & RelevantOperations) != RelevantOperations) {
return 0;
}
return RelevantOperations;
}
return RuleOperations;
}
static BOOLEAN MatchSourceTargetRule(
PDYNAMIC_RULE Rule,
HANDLE SourcePid,
HANDLE TargetPid,
PCUNICODE_STRING SourcePath,
PCUNICODE_STRING TargetPath,
ULONG HandleType,
ULONG ObjectType,
ULONG RiskScore
) {
if (Rule->HandleTypes != 0 && (Rule->HandleTypes & HandleType) == 0) return FALSE;
if (Rule->ObjectTypes != 0 && (Rule->ObjectTypes & ObjectType) == 0) return FALSE;
if (!RiskScoreMatches(Rule, RiskScore)) return FALSE;
if (!EvaluateNodeConflict(Rule->Initiator, Rule->InitiatorExclude, SourcePath)) return FALSE;
if (!EvaluateNodeConflict(Rule->Target, Rule->TargetExclude, TargetPath)) return FALSE;
if (!MatchDirectParent(SourcePid, Rule->InitiatorParent, Rule->InitiatorParentExclude)) return FALSE;
if (!MatchProcessTree(SourcePid, Rule->InitiatorProcessTree, Rule->InitiatorProcessTreeExclude)) return FALSE;
if (!MatchProcessTree(TargetPid, Rule->TargetProcessTree, Rule->TargetProcessTreeExclude)) return FALSE;
return TRUE;
}
BOOLEAN EvaluateProcessCreateRule(
HANDLE CreatorPid,
HANDLE ParentPid,
HANDLE ProcessId,
PCUNICODE_STRING TargetPath,
PCUNICODE_STRING CommandLine,
BOOLEAN FileOpenNameAvailable,
BOOLEAN IsSubsystemProcess,
PULONG OutCode,
PBOOLEAN OutKill
) {
UNREFERENCED_PARAMETER(ProcessId);
if (!OutCode || !OutKill || !CreatorPid) return FALSE;
*OutCode = 0;
*OutKill = FALSE;
if (IsProcessTrusted(CreatorPid)) return FALSE;
PUNICODE_STRING CreatorPath = NULL;
PUNICODE_STRING ParentPath = NULL;
GetProcessPath(CreatorPid, &CreatorPath);
if (ParentPid) GetProcessPath(ParentPid, &ParentPath);
BOOLEAN ParentMismatch = CreatorPid && ParentPid && CreatorPid != ParentPid;
ULONG RiskScore = CalculateProcessCreateRisk(CreatorPid, ParentPid, IsSubsystemProcess);
BOOLEAN Blocked = FALSE;
ULONG SelectedPriority = 0;
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&RuleLock, TRUE);
for (PDYNAMIC_RULE Rule = g_DynamicRules; Rule; Rule = Rule->Next) {
if (Rule->Category != RuleCategoryProcess || (Rule->Operations & OP_EXECUTE) == 0) continue;
if (!TriStateMatches(Rule->ParentMismatch, ParentMismatch)) continue;
if (!TriStateMatches(Rule->FileOpenNameAvailable, FileOpenNameAvailable)) continue;
if (!TriStateMatches(Rule->SubsystemProcess, IsSubsystemProcess)) continue;
if (!RiskScoreMatches(Rule, RiskScore)) continue;
if (!EvaluateNodeConflict(Rule->Initiator, Rule->InitiatorExclude, CreatorPath)) continue;
if (!EvaluateNodeConflict(Rule->Creator, Rule->CreatorExclude, CreatorPath)) continue;
if (!EvaluateNodeConflict(Rule->Parent, Rule->ParentExclude, ParentPath)) continue;
if (!EvaluateNodeConflict(Rule->Target, Rule->TargetExclude, TargetPath)) continue;
if (!MatchDirectParent(CreatorPid, Rule->InitiatorParent, Rule->InitiatorParentExclude)) continue;
if (!MatchProcessTree(CreatorPid, Rule->InitiatorProcessTree, Rule->InitiatorProcessTreeExclude)) continue;
if (!MatchProcessTree(ParentPid, Rule->TargetProcessTree, Rule->TargetProcessTreeExclude)) continue;
if (Rule->CommandLine && !MatchNodeListAny(Rule->CommandLine, CommandLine)) continue;
if (Rule->CommandLineExclude && MatchNodeListAny(Rule->CommandLineExclude, CommandLine)) continue;
if (!CheckRuleThreshold(Rule, CreatorPid)) continue;
if (!Blocked || Rule->Priority >= SelectedPriority) {
*OutCode = Rule->Code;
*OutKill = Rule->Kill;
SelectedPriority = Rule->Priority;
}
Blocked = TRUE;
}
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
if (CreatorPath) ExFreePool(CreatorPath);
if (ParentPath) ExFreePool(ParentPath);
return Blocked;
}
BOOLEAN EvaluateProcessAccessRule(
HANDLE SourcePid,
HANDLE TargetPid,
ULONG RequestedOperations,
ULONG HandleType,
ULONG ObjectType,
PULONG DeniedOperations,
PULONG OutCode,
PBOOLEAN OutKill
) {
if (!DeniedOperations || !OutCode || !OutKill || !SourcePid || !TargetPid || RequestedOperations == 0) return FALSE;
*DeniedOperations = 0;
*OutCode = 0;
*OutKill = FALSE;
if (IsProcessTrusted(SourcePid)) return FALSE;
PUNICODE_STRING SourcePath = NULL;
PUNICODE_STRING TargetPath = NULL;
GetProcessPath(SourcePid, &SourcePath);
GetProcessPath(TargetPid, &TargetPath);
ULONG RiskScore = CalculateAccessRisk(RequestedOperations, HandleType, ObjectType);
ULONG SelectedPriority = 0;
BOOLEAN Matched = FALSE;
const ULONG MemoryOperations =
OP_VM_READ |
OP_VM_WRITE |
OP_VM_OPERATION |
OP_CREATE_THREAD |
OP_THREAD_SET_CONTEXT;
const ULONG ControlOperations =
OP_THREAD_SET_TOKEN |
OP_TERMINATE |
OP_SUSPEND_RESUME |
OP_DUP_HANDLE |
OP_SET_INFORMATION |
OP_CREATE_PROCESS |
OP_IMPERSONATE;
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&RuleLock, TRUE);
for (PDYNAMIC_RULE Rule = g_DynamicRules; Rule; Rule = Rule->Next) {
ULONG CategoryOperations = 0;
if (Rule->Category == RuleCategoryMemory) {
CategoryOperations = RequestedOperations & MemoryOperations;
}
else if (Rule->Category == RuleCategoryProcess) {
CategoryOperations = RequestedOperations & ControlOperations;
}
else {
continue;
}
if (CategoryOperations == 0) continue;
ULONG RuleMatchedOperations = GetMatchedOperations(Rule, CategoryOperations);
if (RuleMatchedOperations == 0) continue;
if (!MatchSourceTargetRule(
Rule,
SourcePid,
TargetPid,
SourcePath,
TargetPath,
HandleType,
ObjectType,
RiskScore
)) continue;
if (!CheckRuleThreshold(Rule, SourcePid)) continue;
*DeniedOperations |= RuleMatchedOperations;
if (!Matched || Rule->Priority >= SelectedPriority) {
*OutCode = Rule->Code;
*OutKill = Rule->Kill;
SelectedPriority = Rule->Priority;
}
Matched = TRUE;
}
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
if (SourcePath) ExFreePool(SourcePath);
if (TargetPath) ExFreePool(TargetPath);
return Matched && *DeniedOperations != 0;
}
BOOLEAN EvaluateThreadRule(
HANDLE SourcePid,
HANDLE TargetPid,
PVOID StartAddress,
ULONG MemoryType,
ULONG MemoryProtection,
SIZE_T RegionSize,
PULONG OutCode,
PBOOLEAN OutKill
) {
UNREFERENCED_PARAMETER(StartAddress);
if (!OutCode || !OutKill || !SourcePid || !TargetPid) return FALSE;
*OutCode = 0;
*OutKill = FALSE;
if (IsProcessTrusted(SourcePid)) return FALSE;
PUNICODE_STRING SourcePath = NULL;
PUNICODE_STRING TargetPath = NULL;
GetProcessPath(SourcePid, &SourcePath);
GetProcessPath(TargetPid, &TargetPath);
ULONG RiskScore = CalculateThreadRisk(MemoryType, MemoryProtection, RegionSize);
ULONG SelectedPriority = 0;
BOOLEAN Matched = FALSE;
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&RuleLock, TRUE);
for (PDYNAMIC_RULE Rule = g_DynamicRules; Rule; Rule = Rule->Next) {
if (Rule->Category != RuleCategoryThread || (Rule->Operations & OP_EXECUTE) == 0) continue;
ULONG RequiredMemoryTypes = Rule->ThreadMemoryTypes != 0
? Rule->ThreadMemoryTypes
: PYAS_MEMORY_PRIVATE;
ULONG RequiredProtections = Rule->ThreadMemoryProtections != 0
? Rule->ThreadMemoryProtections
: PYAS_PROTECT_EXECUTE;
if ((RequiredMemoryTypes & MemoryType) == 0) continue;
if ((RequiredProtections & MemoryProtection) == 0) continue;
if (Rule->MinimumRegionSize > 0 && RegionSize < Rule->MinimumRegionSize) continue;
if (Rule->MaximumRegionSize > 0 && RegionSize > Rule->MaximumRegionSize) continue;
if (!MatchSourceTargetRule(
Rule,
SourcePid,
TargetPid,
SourcePath,
TargetPath,
PYAS_HANDLE_CREATE,
PYAS_OBJECT_THREAD,
RiskScore
)) continue;
if (!CheckRuleThreshold(Rule, SourcePid)) continue;
if (!Matched || Rule->Priority >= SelectedPriority) {
*OutCode = Rule->Code;
*OutKill = Rule->Kill;
SelectedPriority = Rule->Priority;
}
Matched = TRUE;
}
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
if (SourcePath) ExFreePool(SourcePath);
if (TargetPath) ExFreePool(TargetPath);
return Matched;
}
BOOLEAN EvaluateImageLoadRule(
HANDLE ProcessId,
PCUNICODE_STRING ImagePath,
PIMAGE_INFO ImageInfo,
PULONG OutCode,
PBOOLEAN OutKill
) {
if (!OutCode || !OutKill || !ProcessId || !ImagePath || !ImagePath->Buffer || !ImageInfo) return FALSE;
*OutCode = 0;
*OutKill = FALSE;
PUNICODE_STRING ProcessPath = NULL;
GetProcessPath(ProcessId, &ProcessPath);
BOOLEAN Matched = FALSE;
ULONG SelectedPriority = 0;
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&RuleLock, TRUE);
for (PDYNAMIC_RULE Rule = g_DynamicRules; Rule; Rule = Rule->Next) {
if (Rule->Category != RuleCategoryProcess || (Rule->Operations & OP_IMAGE_LOAD) == 0) continue;
if (!EvaluateNodeConflict(Rule->Initiator, Rule->InitiatorExclude, ProcessPath)) continue;
if (!EvaluateNodeConflict(Rule->Target, Rule->TargetExclude, ImagePath)) continue;
if (!MatchDirectParent(ProcessId, Rule->InitiatorParent, Rule->InitiatorParentExclude)) continue;
if (!MatchProcessTree(ProcessId, Rule->InitiatorProcessTree, Rule->InitiatorProcessTreeExclude)) continue;
if (!MatchProcessTree(ProcessId, Rule->TargetProcessTree, Rule->TargetProcessTreeExclude)) continue;
if (Rule->MinimumRegionSize > 0 && ImageInfo->ImageSize < Rule->MinimumRegionSize) continue;
if (Rule->MaximumRegionSize > 0 && ImageInfo->ImageSize > Rule->MaximumRegionSize) continue;
if (!CheckRuleThreshold(Rule, ProcessId)) continue;
if (!Matched || Rule->Priority >= SelectedPriority) {
*OutCode = Rule->Code;
*OutKill = Rule->Kill;
SelectedPriority = Rule->Priority;
}
Matched = TRUE;
}
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
if (ProcessPath) ExFreePool(ProcessPath);
return Matched;
}
BOOLEAN EvaluateProcessRule(
HANDLE ProcessId,
PCUNICODE_STRING TargetPath,
PCUNICODE_STRING CommandLine,
PULONG OutCode,
PBOOLEAN OutKill
) {
return EvaluateProcessCreateRule(
ProcessId,
NULL,
NULL,
TargetPath,
CommandLine,
TRUE,
FALSE,
OutCode,
OutKill
);
}
BOOLEAN EvaluateFileRule(HANDLE ProcessId, PCUNICODE_STRING TargetPath, ULONG Operation, PVOID WriteBuffer, ULONG WriteLength, PULONG OutCode, PBOOLEAN OutKill) {
UNREFERENCED_PARAMETER(WriteBuffer);
UNREFERENCED_PARAMETER(WriteLength);
if (!OutCode || !OutKill) return FALSE;
*OutCode = 0;
*OutKill = FALSE;
if (IsProcessTrusted(ProcessId)) return FALSE;
PUNICODE_STRING InitiatorPath = NULL;
GetProcessImageName(ProcessId, &InitiatorPath);
BOOLEAN Blocked = FALSE;
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&RuleLock, TRUE);
PDYNAMIC_RULE Rule = g_DynamicRules;
while (Rule) {
if (Rule->Category == RuleCategoryFile && (Rule->Operations & Operation)) {
BOOLEAN Match = TRUE;
if (!EvaluateNodeConflict(Rule->Initiator, Rule->InitiatorExclude, InitiatorPath)) Match = FALSE;
if (Match && !EvaluateNodeConflict(Rule->Target, Rule->TargetExclude, TargetPath)) Match = FALSE;
if (Match && Rule->Extensions && !MatchExtensionList(Rule->Extensions, TargetPath)) Match = FALSE;
if (Match && CheckRuleThreshold(Rule, ProcessId)) {
*OutCode = Rule->Code;
*OutKill = Rule->Kill;
Blocked = TRUE;
break;
}
}
Rule = Rule->Next;
}
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
if (InitiatorPath) ExFreePool(InitiatorPath);
return Blocked;
}
BOOLEAN EvaluateRegistryRule(HANDLE ProcessId, PCUNICODE_STRING KeyName, ULONG Operation, PULONG OutCode, PBOOLEAN OutKill) {
if (!OutCode || !OutKill) return FALSE;
*OutCode = 0;
*OutKill = FALSE;
if (IsProcessTrusted(ProcessId)) return FALSE;
PUNICODE_STRING InitiatorPath = NULL;
GetProcessImageName(ProcessId, &InitiatorPath);
BOOLEAN Blocked = FALSE;
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&RuleLock, TRUE);
PDYNAMIC_RULE Rule = g_DynamicRules;
while (Rule) {
if (Rule->Category == RuleCategoryRegistry && (Rule->Operations & Operation)) {
BOOLEAN Match = TRUE;
if (!EvaluateNodeConflict(Rule->Initiator, Rule->InitiatorExclude, InitiatorPath)) Match = FALSE;
if (Match && !EvaluateNodeConflict(Rule->Target, Rule->TargetExclude, KeyName)) Match = FALSE;
if (Match && CheckRuleThreshold(Rule, ProcessId)) {
*OutCode = Rule->Code;
*OutKill = Rule->Kill;
Blocked = TRUE;
break;
}
}
Rule = Rule->Next;
}
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
if (InitiatorPath) ExFreePool(InitiatorPath);
return Blocked;
}
BOOLEAN EvaluateDeviceRule(HANDLE ProcessId, PULONG OutCode, PBOOLEAN OutKill) {
if (!OutCode || !OutKill) return FALSE;
*OutCode = 0;
*OutKill = FALSE;
if (IsProcessTrusted(ProcessId)) return FALSE;
PUNICODE_STRING InitiatorPath = NULL;
GetProcessImageName(ProcessId, &InitiatorPath);
BOOLEAN Blocked = FALSE;
KeEnterCriticalRegion();
ExAcquireResourceSharedLite(&RuleLock, TRUE);
PDYNAMIC_RULE Rule = g_DynamicRules;
while (Rule) {
if (Rule->Category == RuleCategoryDevice && (Rule->Operations & OP_IOCTL)) {
BOOLEAN Match = TRUE;
if (!EvaluateNodeConflict(Rule->Initiator, Rule->InitiatorExclude, InitiatorPath)) Match = FALSE;
if (Match && CheckRuleThreshold(Rule, ProcessId)) {
*OutCode = Rule->Code;
*OutKill = Rule->Kill;
Blocked = TRUE;
break;
}
}
Rule = Rule->Next;
}
ExReleaseResourceLite(&RuleLock);
KeLeaveCriticalRegion();
if (InitiatorPath) ExFreePool(InitiatorPath);
return Blocked;
}
BOOLEAN EvaluateMemoryRule(HANDLE SourcePid, HANDLE TargetPid, ULONG Operation, PULONG OutCode, PBOOLEAN OutKill) {
ULONG DeniedOperations = 0;
return EvaluateProcessAccessRule(
SourcePid,
TargetPid,
Operation,
PYAS_HANDLE_CREATE,
PYAS_OBJECT_PROCESS,
&DeniedOperations,
OutCode,
OutKill
);
}
BOOLEAN EvaluateProcessHandleRule(HANDLE SourcePid, HANDLE TargetPid, ULONG Operation, PULONG OutCode, PBOOLEAN OutKill) {
ULONG DeniedOperations = 0;
return EvaluateProcessAccessRule(
SourcePid,
TargetPid,
Operation,
PYAS_HANDLE_CREATE,
PYAS_OBJECT_PROCESS,
&DeniedOperations,
OutCode,
OutKill
);
}
NTSTATUS SendMessageToUser(ULONG Code, ULONG Pid, PWCHAR Path, USHORT PathSize) {
if (KeGetCurrentIrql() > APC_LEVEL) return STATUS_UNSUCCESSFUL;
LONG DriverState = InterlockedCompareExchange(
&GlobalData.DriverState,
PyasDriverStateCold,
PyasDriverStateCold
);
if (DriverState != PyasDriverStateRunning && DriverState != PyasDriverStateStopRetry) {
return STATUS_PORT_DISCONNECTED;
}
if (InterlockedCompareExchange(&GlobalData.PortStopping, 0, 0) != 0) return STATUS_PORT_DISCONNECTED;
if (!ExAcquireRundownProtection(&GlobalData.PortRundown)) return STATUS_PORT_DISCONNECTED;
NTSTATUS status = STATUS_PORT_DISCONNECTED;
if (GlobalData.ClientPort) {
PPYAS_MESSAGE msg = (PPYAS_MESSAGE)PyasAllocate(sizeof(PYAS_MESSAGE));
if (msg) {
RtlZeroMemory(msg, sizeof(*msg));
msg->MessageCode = Code;
msg->ProcessId = Pid;
if (Path && PathSize > 0) {
size_t MaxSize = sizeof(msg->Path) - sizeof(WCHAR);
size_t BytesToCopy = PathSize > MaxSize ? MaxSize : PathSize;
__try {
RtlCopyMemory(msg->Path, Path, BytesToCopy);
msg->Path[BytesToCopy / sizeof(WCHAR)] = L'\0';
}
__except (EXCEPTION_EXECUTE_HANDLER) {
PyasFree(msg);
ExReleaseRundownProtection(&GlobalData.PortRundown);
return STATUS_ACCESS_VIOLATION;
}
}
LARGE_INTEGER timeout;
timeout.QuadPart = (KeGetCurrentIrql() == PASSIVE_LEVEL) ? -(5 * 10000) : 0;
status = FltSendMessage(GlobalData.FilterHandle, &GlobalData.ClientPort, msg, sizeof(PYAS_MESSAGE), NULL, NULL, &timeout);
PyasFree(msg);
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
ExReleaseRundownProtection(&GlobalData.PortRundown);
return status;
}