mirror of
https://github.com/EynaExp/MiMiNi-EDR
synced 2026-08-09 12:06:20 +00:00
Initial commit
This commit is contained in:
+57
@@ -0,0 +1,57 @@
|
||||
# Build artifacts
|
||||
*.obj
|
||||
*.o
|
||||
*.exe
|
||||
*.dll
|
||||
*.sys
|
||||
*.pdb
|
||||
*.ilk
|
||||
*.exp
|
||||
*.lib
|
||||
*.ipdb
|
||||
*.iobj
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Build directories
|
||||
Debug/
|
||||
Release/
|
||||
x64/
|
||||
x86/
|
||||
build/
|
||||
out/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# NuGet
|
||||
packages/
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Editor
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Temp
|
||||
~/
|
||||
@@ -0,0 +1,105 @@
|
||||
# EDR - Endpoint Detection & Response
|
||||
|
||||
A Windows kernel-mode EDR agent that monitors system activity and detects malicious behavior in real-time.
|
||||
|
||||
## Architecture
|
||||
|
||||
The project consists of two components:
|
||||
|
||||
### Kernel Driver (`driver.cpp`)
|
||||
- Registers process and thread creation callbacks via `PsSetCreateProcessNotifyRoutineEx` and `PsSetCreateThreadNotifyRoutine`
|
||||
- Implements LSASS protection using `ObRegisterCallbacks` to strip `PROCESS_VM_READ` and `PROCESS_QUERY_INFORMATION` access from LSASS handles
|
||||
- Manages an event queue with IRP-based communication to the user-mode agent
|
||||
- Supports configurable block rules for process creation
|
||||
|
||||
### User-Mode Agent (`Agent.cpp`)
|
||||
- Communicates with the kernel driver via IOCTLs
|
||||
- Receives and displays real-time process/thread events
|
||||
- Implements detection rules for:
|
||||
- Credential dumping tools (mimikatz, sekurlsa)
|
||||
- LSASS dump attempts (procdump, Task Manager, comsvcs.dll, rundll32, sqldumper, dumpert, nanodump)
|
||||
- Suspicious command-line activity
|
||||
- Can terminate malicious processes via the driver
|
||||
|
||||
## Detection Rules
|
||||
|
||||
| Rule | Description |
|
||||
|------|-------------|
|
||||
| Credential Dumping | Detects mimikatz, sekurlsa, kerberos::list |
|
||||
| LSASS via procdump | Detects procdump targeting lsass |
|
||||
| LSASS via Task Manager | Detects Task Manager dump attempts |
|
||||
| LSASS via comsvcs.dll | Detects comsvcs.dll MiniDump abuse |
|
||||
| LSASS via rundll32 | Detects rundll32 comsvcs MiniDump |
|
||||
| LSASS via sqldumper | Detects sqldumper targeting lsass |
|
||||
| LSASS via dumpert | Detects dumpert.exe |
|
||||
| LSASS via nanodump | Detects nanodump |
|
||||
| LSASS via MiniDumpWriteDump | Detects dbghelp/dbgcore abuse |
|
||||
|
||||
## Block Rules
|
||||
|
||||
The kernel driver supports runtime block rules that match on:
|
||||
- **Image suffix** - process name match (e.g., `cmd.exe`)
|
||||
- **Command-line substring** - command-line content match (e.g., `whoami`)
|
||||
|
||||
Default block rules:
|
||||
- `cmd.exe` + `whoami`
|
||||
- `cmd.exe` + `net user`
|
||||
- `cmd.exe` + `net group`
|
||||
- `powershell.exe` + `mimikatz`
|
||||
- `powershell.exe` + `sekurlsa`
|
||||
|
||||
## Build Requirements
|
||||
|
||||
- Windows 10/11
|
||||
- Visual Studio with C++ Desktop workload
|
||||
- Windows Driver Kit (WDK)
|
||||
- Test signing enabled (`bcdedit /set testsigning on`)
|
||||
|
||||
## Building
|
||||
|
||||
1. Open the solution in Visual Studio
|
||||
2. Build the driver project (x64 Release)
|
||||
3. Build the agent project (x64 Release)
|
||||
|
||||
## Usage
|
||||
|
||||
1. Load the kernel driver:
|
||||
```
|
||||
sc create EDRTEST type= kernel binPath= C:\path\to\driver.sys
|
||||
sc start EDRTEST
|
||||
```
|
||||
|
||||
2. Run the agent:
|
||||
```
|
||||
Agent.exe
|
||||
```
|
||||
|
||||
3. The agent will display real-time events and take action on detected threats.
|
||||
|
||||
4. Stop and remove the driver:
|
||||
```
|
||||
sc stop EDRTEST
|
||||
sc delete EDRTEST
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
EDR/
|
||||
├── src/
|
||||
│ ├── Agent.cpp # User-mode agent
|
||||
│ └── driver.cpp # Kernel-mode driver
|
||||
├── docs/ # Documentation
|
||||
├── .gitignore
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## IOCTL Codes
|
||||
|
||||
| Code | Value | Description |
|
||||
|------|-------|-------------|
|
||||
| IOCTL_WAIT_FOR_EVENT | 0x800 | Wait for next event from driver |
|
||||
| IOCTL_KILL_PROCESS | 0x801 | Terminate a process by PID |
|
||||
| IOCTL_ADD_BLOCK_RULE | 0x802 | Add a block rule |
|
||||
| IOCTL_CLEAR_BLOCK_RULES | 0x803 | Clear all block rules |
|
||||
| IOCTL_SIGNAL_LSASS_DUMP | 0x804 | Signal LSASS dump detection |
|
||||
+562
@@ -0,0 +1,562 @@
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
|
||||
// =======================================================
|
||||
// IOCTLs (must match driver)
|
||||
// =======================================================
|
||||
|
||||
#define IOCTL_WAIT_FOR_EVENT \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_KILL_PROCESS \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ADD_BLOCK_RULE \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_CLEAR_BLOCK_RULES \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_SIGNAL_LSASS_DUMP \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define EVENT_TYPE_PROCESS_CREATE 1
|
||||
#define EVENT_TYPE_PROCESS_EXIT 2
|
||||
#define EVENT_TYPE_THREAD_CREATE 3
|
||||
#define EVENT_TYPE_THREAD_EXIT 4
|
||||
#define EVENT_TYPE_LSASS_ACCESS 5
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct _EDR_EVENT {
|
||||
ULONG EventType;
|
||||
LARGE_INTEGER Timestamp;
|
||||
ULONG64 ProcessId;
|
||||
ULONG64 ThreadId;
|
||||
ULONG64 ParentProcessId;
|
||||
BOOLEAN Blocked;
|
||||
WCHAR ImageFileName[260];
|
||||
WCHAR CommandLine[512];
|
||||
} EDR_EVENT;
|
||||
|
||||
typedef struct _EDR_COMMAND {
|
||||
ULONG Action;
|
||||
ULONG64 ProcessId;
|
||||
} EDR_COMMAND;
|
||||
|
||||
typedef struct _BLOCK_RULE_ENTRY {
|
||||
WCHAR ImageSuffix[260];
|
||||
WCHAR CmdLineSubstr[512];
|
||||
} BLOCK_RULE_ENTRY;
|
||||
#pragma pack(pop)
|
||||
|
||||
// =======================================================
|
||||
// Console colors
|
||||
// =======================================================
|
||||
|
||||
#define COLOR_RESET 7
|
||||
#define COLOR_RED 12
|
||||
#define COLOR_GREEN 10
|
||||
#define COLOR_YELLOW 14
|
||||
#define COLOR_CYAN 11
|
||||
#define COLOR_MAGENTA 13
|
||||
|
||||
void SetConsoleColor(WORD color) {
|
||||
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Global shutdown flag
|
||||
// =======================================================
|
||||
|
||||
volatile BOOL g_Running = TRUE;
|
||||
|
||||
void SignalHandler(int signal) {
|
||||
if (signal == SIGINT) {
|
||||
printf("\n");
|
||||
SetConsoleColor(COLOR_YELLOW);
|
||||
printf("[!] Ctrl+C detected. Shutting down...\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
g_Running = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Timestamp formatting
|
||||
// =======================================================
|
||||
|
||||
void FormatTimestamp(LARGE_INTEGER timestamp, char* buffer, size_t bufSize) {
|
||||
FILETIME ft;
|
||||
SYSTEMTIME st;
|
||||
|
||||
ft.dwLowDateTime = timestamp.LowPart;
|
||||
ft.dwHighDateTime = timestamp.HighPart;
|
||||
|
||||
FileTimeToSystemTime(&ft, &st);
|
||||
|
||||
sprintf_s(buffer, bufSize, "%02d:%02d:%02d.%03d",
|
||||
st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Event type to string
|
||||
// =======================================================
|
||||
|
||||
const char* EventTypeToString(ULONG eventType) {
|
||||
switch (eventType) {
|
||||
case EVENT_TYPE_PROCESS_CREATE: return "PROCESS_CREATE";
|
||||
case EVENT_TYPE_PROCESS_EXIT: return "PROCESS_EXIT";
|
||||
case EVENT_TYPE_THREAD_CREATE: return "THREAD_CREATE";
|
||||
case EVENT_TYPE_THREAD_EXIT: return "THREAD_EXIT";
|
||||
case EVENT_TYPE_LSASS_ACCESS: return "LSASS_ACCESS";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Kill process via driver
|
||||
// =======================================================
|
||||
|
||||
BOOL KillProcess(HANDLE hDevice, ULONG64 processId) {
|
||||
EDR_COMMAND cmd = { 0 };
|
||||
cmd.Action = 1;
|
||||
cmd.ProcessId = processId;
|
||||
|
||||
DWORD ret = 0;
|
||||
|
||||
BOOL ok = DeviceIoControl(
|
||||
hDevice,
|
||||
IOCTL_KILL_PROCESS,
|
||||
&cmd, sizeof(cmd),
|
||||
NULL, 0,
|
||||
&ret, NULL);
|
||||
|
||||
if (!ok) {
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[-] Failed to kill process %llu (err=%lu)\n",
|
||||
processId, GetLastError());
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
SetConsoleColor(COLOR_GREEN);
|
||||
printf("[+] Kill command sent for PID %llu\n", processId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Signal LSASS dump attempt to kernel
|
||||
// =======================================================
|
||||
|
||||
BOOL SignalLsassDump(HANDLE hDevice, ULONG64 processId) {
|
||||
EDR_COMMAND cmd = { 0 };
|
||||
cmd.Action = 1;
|
||||
cmd.ProcessId = processId;
|
||||
|
||||
DWORD ret = 0;
|
||||
|
||||
BOOL ok = DeviceIoControl(
|
||||
hDevice,
|
||||
IOCTL_SIGNAL_LSASS_DUMP,
|
||||
&cmd, sizeof(cmd),
|
||||
NULL, 0,
|
||||
&ret, NULL);
|
||||
|
||||
if (!ok) {
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[-] Failed to signal LSASS dump for PID %llu (err=%lu)\n",
|
||||
processId, GetLastError());
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
SetConsoleColor(COLOR_MAGENTA);
|
||||
printf("[+] LSASS dump signal sent to kernel for PID %llu\n", processId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Block rule helpers
|
||||
// =======================================================
|
||||
|
||||
BOOL AddBlockRule(HANDLE hDevice, PCWSTR suffix, PCWSTR cmdSubstr) {
|
||||
BLOCK_RULE_ENTRY rule = { 0 };
|
||||
if (suffix) wcscpy_s(rule.ImageSuffix, _countof(rule.ImageSuffix), suffix);
|
||||
if (cmdSubstr) wcscpy_s(rule.CmdLineSubstr, _countof(rule.CmdLineSubstr), cmdSubstr);
|
||||
|
||||
DWORD ret = 0;
|
||||
BOOL ok = DeviceIoControl(
|
||||
hDevice,
|
||||
IOCTL_ADD_BLOCK_RULE,
|
||||
&rule, sizeof(rule),
|
||||
NULL, 0,
|
||||
&ret, NULL);
|
||||
|
||||
if (!ok) {
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[-] Failed to add block rule (err=%lu)\n", GetLastError());
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return FALSE;
|
||||
}
|
||||
SetConsoleColor(COLOR_GREEN);
|
||||
wprintf(L"[+] Block rule added: \"%s\" + \"%s\"\n",
|
||||
rule.ImageSuffix, rule.CmdLineSubstr);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL ClearBlockRules(HANDLE hDevice) {
|
||||
DWORD ret = 0;
|
||||
BOOL ok = DeviceIoControl(
|
||||
hDevice,
|
||||
IOCTL_CLEAR_BLOCK_RULES,
|
||||
NULL, 0,
|
||||
NULL, 0,
|
||||
&ret, NULL);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Initialize kernel block rules
|
||||
// =======================================================
|
||||
|
||||
void InitializeBlockRules(HANDLE hDevice) {
|
||||
ClearBlockRules(hDevice);
|
||||
|
||||
SetConsoleColor(COLOR_CYAN);
|
||||
printf("[*] Initializing block rules...\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
|
||||
AddBlockRule(hDevice, L"cmd.exe", L"whoami");
|
||||
AddBlockRule(hDevice, L"cmd.exe", L"net user");
|
||||
AddBlockRule(hDevice, L"cmd.exe", L"net group");
|
||||
AddBlockRule(hDevice, L"powershell.exe", L"mimikatz");
|
||||
AddBlockRule(hDevice, L"powershell.exe", L"sekurlsa");
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Display event with formatting
|
||||
// =======================================================
|
||||
|
||||
void DisplayEvent(EDR_EVENT* ev) {
|
||||
char timeBuf[32];
|
||||
FormatTimestamp(ev->Timestamp, timeBuf, sizeof(timeBuf));
|
||||
|
||||
WORD eventColor = COLOR_RESET;
|
||||
const char* icon = "";
|
||||
|
||||
switch (ev->EventType) {
|
||||
case EVENT_TYPE_PROCESS_CREATE:
|
||||
eventColor = ev->Blocked ? COLOR_RED : COLOR_GREEN;
|
||||
icon = ev->Blocked ? "[BLOCKED]" : "[CREATE]";
|
||||
break;
|
||||
case EVENT_TYPE_PROCESS_EXIT:
|
||||
eventColor = COLOR_YELLOW;
|
||||
icon = "[EXIT]";
|
||||
break;
|
||||
case EVENT_TYPE_THREAD_CREATE:
|
||||
eventColor = COLOR_CYAN;
|
||||
icon = "[THREAD+]";
|
||||
break;
|
||||
case EVENT_TYPE_THREAD_EXIT:
|
||||
eventColor = COLOR_CYAN;
|
||||
icon = "[THREAD-]";
|
||||
break;
|
||||
case EVENT_TYPE_LSASS_ACCESS:
|
||||
eventColor = COLOR_MAGENTA;
|
||||
icon = "[LSASS!]";
|
||||
break;
|
||||
default:
|
||||
eventColor = COLOR_RESET;
|
||||
icon = "[EVENT]";
|
||||
break;
|
||||
}
|
||||
|
||||
SetConsoleColor(eventColor);
|
||||
printf("%s %s | PID: %6llu | Type: %s",
|
||||
icon, timeBuf, ev->ProcessId, EventTypeToString(ev->EventType));
|
||||
|
||||
if (ev->EventType == EVENT_TYPE_LSASS_ACCESS) {
|
||||
printf(" | LSASS access stripped");
|
||||
}
|
||||
|
||||
if (wcslen(ev->ImageFileName) > 0) {
|
||||
wprintf(L" | Image: %s", ev->ImageFileName);
|
||||
}
|
||||
|
||||
if (ev->EventType == EVENT_TYPE_PROCESS_CREATE && wcslen(ev->CommandLine) > 0) {
|
||||
wprintf(L"\n CmdLine: %s", ev->CommandLine);
|
||||
}
|
||||
|
||||
if (ev->Blocked) {
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf(" [BLOCKED]");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// LSASS dumping detection (user-mode rules)
|
||||
// =======================================================
|
||||
|
||||
BOOL AnalyzeEvent(HANDLE hDevice, EDR_EVENT* ev) {
|
||||
|
||||
DisplayEvent(ev);
|
||||
|
||||
if (ev->EventType == EVENT_TYPE_PROCESS_CREATE) {
|
||||
|
||||
// ---- Rule 1: Kill notepad.exe ----
|
||||
size_t len = wcslen(ev->ImageFileName);
|
||||
if (len >= 11) {
|
||||
WCHAR* end = ev->ImageFileName + len - 11;
|
||||
if (_wcsicmp(end, L"notepad.exe") == 0) {
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] notepad.exe detected\n");
|
||||
printf("[ACTION] Terminating PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return KillProcess(hDevice, ev->ProcessId);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Rule 2: Credential dumping tools ----
|
||||
if (wcsstr(ev->CommandLine, L"mimikatz") ||
|
||||
wcsstr(ev->CommandLine, L"sekurlsa") ||
|
||||
wcsstr(ev->CommandLine, L"kerberos::list")) {
|
||||
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] Credential dumping attempt (mimikatz/sekurlsa)\n");
|
||||
printf("[ACTION] Terminating PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return KillProcess(hDevice, ev->ProcessId);
|
||||
}
|
||||
|
||||
// ---- Rule 3: LSASS dumping - procdump ----
|
||||
if (wcsstr(ev->ImageFileName, L"procdump") ||
|
||||
wcsstr(ev->ImageFileName, L"procdump64") ||
|
||||
wcsstr(ev->ImageFileName, L"procdump.exe")) {
|
||||
|
||||
if (wcsstr(ev->CommandLine, L"lsass") ||
|
||||
wcsstr(ev->CommandLine, L"-ma") ||
|
||||
wcsstr(ev->CommandLine, L"-mm")) {
|
||||
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] LSASS dump attempt via procdump\n");
|
||||
printf("[ACTION] Signaling kernel to block and terminate PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return SignalLsassDump(hDevice, ev->ProcessId);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Rule 4: LSASS dumping - Task Manager dump ----
|
||||
if (wcsstr(ev->ImageFileName, L"taskmgr.exe") ||
|
||||
wcsstr(ev->ImageFileName, L"Taskmgr.exe")) {
|
||||
|
||||
if (wcsstr(ev->CommandLine, L"lsass") ||
|
||||
wcsstr(ev->CommandLine, L"minidump")) {
|
||||
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] LSASS dump attempt via Task Manager\n");
|
||||
printf("[ACTION] Signaling kernel to block and terminate PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return SignalLsassDump(hDevice, ev->ProcessId);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Rule 5: LSASS dumping - comsvcs.dll ----
|
||||
if (wcsstr(ev->CommandLine, L"comsvcs.dll") ||
|
||||
wcsstr(ev->CommandLine, L"MiniDump") ||
|
||||
wcsstr(ev->CommandLine, L"#24")) {
|
||||
|
||||
if (wcsstr(ev->CommandLine, L"lsass")) {
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] LSASS dump attempt via comsvcs.dll\n");
|
||||
printf("[ACTION] Signaling kernel to block and terminate PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return SignalLsassDump(hDevice, ev->ProcessId);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Rule 6: LSASS dumping - rundll32 comsvcs ----
|
||||
if (wcsstr(ev->ImageFileName, L"rundll32.exe") &&
|
||||
wcsstr(ev->CommandLine, L"comsvcs") &&
|
||||
wcsstr(ev->CommandLine, L"MiniDump")) {
|
||||
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] LSASS dump attempt via rundll32 comsvcs\n");
|
||||
printf("[ACTION] Signaling kernel to block and terminate PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return SignalLsassDump(hDevice, ev->ProcessId);
|
||||
}
|
||||
|
||||
// ---- Rule 7: LSASS dumping - sqldumper ----
|
||||
if (wcsstr(ev->ImageFileName, L"sqldumper.exe") &&
|
||||
wcsstr(ev->CommandLine, L"lsass")) {
|
||||
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] LSASS dump attempt via sqldumper\n");
|
||||
printf("[ACTION] Signaling kernel to block and terminate PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return SignalLsassDump(hDevice, ev->ProcessId);
|
||||
}
|
||||
|
||||
// ---- Rule 8: LSASS dumping - dumpert ----
|
||||
if (wcsstr(ev->ImageFileName, L"dumpert.exe") ||
|
||||
wcsstr(ev->ImageFileName, L"Dumpert.exe") ||
|
||||
wcsstr(ev->CommandLine, L"Outflank-Dumpert")) {
|
||||
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] LSASS dump attempt via dumpert\n");
|
||||
printf("[ACTION] Signaling kernel to block and terminate PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return SignalLsassDump(hDevice, ev->ProcessId);
|
||||
}
|
||||
|
||||
// ---- Rule 9: LSASS dumping - nanodump ----
|
||||
if (wcsstr(ev->ImageFileName, L"nanodump") ||
|
||||
wcsstr(ev->CommandLine, L"nanodump")) {
|
||||
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] LSASS dump attempt via nanodump\n");
|
||||
printf("[ACTION] Signaling kernel to block and terminate PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return SignalLsassDump(hDevice, ev->ProcessId);
|
||||
}
|
||||
|
||||
// ---- Rule 10: LSASS dumping - direct syscalls ----
|
||||
if (wcsstr(ev->CommandLine, L"MiniDumpWriteDump") ||
|
||||
wcsstr(ev->CommandLine, L"dbghelp") ||
|
||||
wcsstr(ev->CommandLine, L"dbgcore")) {
|
||||
|
||||
if (wcsstr(ev->CommandLine, L"lsass") ||
|
||||
wcsstr(ev->CommandLine, L"LSASS")) {
|
||||
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[DETECTION] LSASS dump attempt via MiniDumpWriteDump\n");
|
||||
printf("[ACTION] Signaling kernel to block and terminate PID %llu\n", ev->ProcessId);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return SignalLsassDump(hDevice, ev->ProcessId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// Main
|
||||
// =======================================================
|
||||
|
||||
int main()
|
||||
{
|
||||
signal(SIGINT, SignalHandler);
|
||||
|
||||
SetConsoleColor(COLOR_CYAN);
|
||||
printf("========================================\n");
|
||||
printf(" EDR Agent - Endpoint Detection & Response\n");
|
||||
printf("========================================\n\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
|
||||
HANDLE hDevice = CreateFileW(
|
||||
L"\\\\.\\EDRTEST",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
NULL);
|
||||
|
||||
if (hDevice == INVALID_HANDLE_VALUE) {
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[-] Failed to open device: %lu\n", GetLastError());
|
||||
printf("[-] Make sure driver is loaded\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
return 1;
|
||||
}
|
||||
|
||||
SetConsoleColor(COLOR_GREEN);
|
||||
printf("[+] Device opened successfully\n\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
|
||||
InitializeBlockRules(hDevice);
|
||||
|
||||
printf("\n");
|
||||
SetConsoleColor(COLOR_CYAN);
|
||||
printf("[*] Listening for events (Ctrl+C to stop)\n");
|
||||
printf("========================================\n\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
|
||||
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (!hEvent) {
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[-] Failed to create event: %lu\n", GetLastError());
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
CloseHandle(hDevice);
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (g_Running)
|
||||
{
|
||||
EDR_EVENT ev = { 0 };
|
||||
DWORD bytesReturned = 0;
|
||||
|
||||
OVERLAPPED ov;
|
||||
ZeroMemory(&ov, sizeof(ov));
|
||||
ov.hEvent = hEvent;
|
||||
|
||||
BOOL ok = DeviceIoControl(
|
||||
hDevice,
|
||||
IOCTL_WAIT_FOR_EVENT,
|
||||
NULL, 0,
|
||||
&ev, sizeof(ev),
|
||||
&bytesReturned,
|
||||
&ov);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
DWORD err = GetLastError();
|
||||
|
||||
if (err != ERROR_IO_PENDING)
|
||||
{
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[-] DeviceIoControl failed: %lu\n", err);
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
|
||||
if (err == ERROR_BUSY) {
|
||||
Sleep(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!GetOverlappedResult(hDevice, &ov, &bytesReturned, TRUE))
|
||||
{
|
||||
SetConsoleColor(COLOR_RED);
|
||||
printf("[-] GetOverlappedResult failed: %lu\n", GetLastError());
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AnalyzeEvent(hDevice, &ev);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
SetConsoleColor(COLOR_YELLOW);
|
||||
printf("[+] Cleaning up...\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
|
||||
CancelIoEx(hDevice, NULL);
|
||||
|
||||
CloseHandle(hEvent);
|
||||
CloseHandle(hDevice);
|
||||
|
||||
SetConsoleColor(COLOR_GREEN);
|
||||
printf("[+] Shutdown complete\n");
|
||||
SetConsoleColor(COLOR_RESET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+756
@@ -0,0 +1,756 @@
|
||||
#include <ntifs.h>
|
||||
#include <ntddk.h>
|
||||
|
||||
|
||||
|
||||
#define IOCTL_WAIT_FOR_EVENT \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_KILL_PROCESS \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ADD_BLOCK_RULE \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_CLEAR_BLOCK_RULES \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_SIGNAL_LSASS_DUMP \
|
||||
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define EVENT_TYPE_PROCESS_CREATE 1
|
||||
#define EVENT_TYPE_PROCESS_EXIT 2
|
||||
#define EVENT_TYPE_THREAD_CREATE 3
|
||||
#define EVENT_TYPE_THREAD_EXIT 4
|
||||
#define EVENT_TYPE_LSASS_ACCESS 5
|
||||
|
||||
#define BLOCK_RULE_MAX_ENTRIES 64
|
||||
|
||||
#ifndef PROCESS_TERMINATE
|
||||
#define PROCESS_TERMINATE (0x0001)
|
||||
#endif
|
||||
|
||||
#ifndef PROCESS_QUERY_INFORMATION
|
||||
#define PROCESS_QUERY_INFORMATION (0x0400)
|
||||
#endif
|
||||
|
||||
#ifndef PROCESS_VM_READ
|
||||
#define PROCESS_VM_READ (0x0010)
|
||||
#endif
|
||||
|
||||
#ifndef STATUS_KILLED
|
||||
#define STATUS_KILLED ((NTSTATUS)0x00000128L)
|
||||
#endif
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct _EDR_EVENT {
|
||||
ULONG EventType;
|
||||
LARGE_INTEGER Timestamp;
|
||||
ULONG64 ProcessId;
|
||||
ULONG64 ThreadId;
|
||||
ULONG64 ParentProcessId;
|
||||
BOOLEAN Blocked;
|
||||
WCHAR ImageFileName[260];
|
||||
WCHAR CommandLine[512];
|
||||
} EDR_EVENT, * PEDR_EVENT;
|
||||
|
||||
typedef struct _EDR_COMMAND {
|
||||
ULONG Action;
|
||||
ULONG64 ProcessId;
|
||||
} EDR_COMMAND, * PEDR_COMMAND;
|
||||
|
||||
typedef struct _BLOCK_RULE_ENTRY {
|
||||
WCHAR ImageSuffix[260];
|
||||
WCHAR CmdLineSubstr[512];
|
||||
} BLOCK_RULE_ENTRY, * PBLOCK_RULE_ENTRY;
|
||||
#pragma pack(pop)
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Internal queue entry
|
||||
// -------------------------------------------------------
|
||||
|
||||
typedef struct _EVENT_ENTRY {
|
||||
LIST_ENTRY ListEntry;
|
||||
EDR_EVENT Event;
|
||||
} EVENT_ENTRY, * PEVENT_ENTRY;
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Driver state
|
||||
// -------------------------------------------------------
|
||||
|
||||
typedef struct _DRIVER_STATE {
|
||||
LIST_ENTRY EventQueue;
|
||||
KSPIN_LOCK QueueLock;
|
||||
PIRP PendingIrp;
|
||||
KSPIN_LOCK IrpLock;
|
||||
} DRIVER_STATE, * PDRIVER_STATE;
|
||||
|
||||
typedef struct _BLOCK_RULE_TABLE {
|
||||
BLOCK_RULE_ENTRY Rules[BLOCK_RULE_MAX_ENTRIES];
|
||||
ULONG Count;
|
||||
KSPIN_LOCK Lock;
|
||||
} BLOCK_RULE_TABLE;
|
||||
|
||||
static DRIVER_STATE g_State;
|
||||
static BLOCK_RULE_TABLE g_BlockRules = { 0 };
|
||||
PDEVICE_OBJECT g_DeviceObject = NULL;
|
||||
PVOID g_ObRegistrationHandle = NULL;
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Forward declarations
|
||||
// -------------------------------------------------------
|
||||
|
||||
DRIVER_UNLOAD DriverUnload;
|
||||
|
||||
_Dispatch_type_(IRP_MJ_CREATE)
|
||||
_Dispatch_type_(IRP_MJ_CLOSE)
|
||||
DRIVER_DISPATCH DispatchCreateClose;
|
||||
|
||||
_Dispatch_type_(IRP_MJ_DEVICE_CONTROL)
|
||||
DRIVER_DISPATCH DispatchIoControl;
|
||||
|
||||
OB_PREOP_CALLBACK_STATUS PreProcessHandleCallback(
|
||||
PVOID RegistrationContext,
|
||||
POB_PRE_OPERATION_INFORMATION OperationInformation
|
||||
);
|
||||
|
||||
static VOID SafeCopyUnicode(
|
||||
_Out_writes_z_(dstMax) PWCHAR dst,
|
||||
_In_ SIZE_T dstMax,
|
||||
_In_ PCUNICODE_STRING src
|
||||
);
|
||||
|
||||
// -------------------------------------------------------
|
||||
// EnqueueEvent
|
||||
// -------------------------------------------------------
|
||||
|
||||
static VOID EnqueueEvent(_In_ PEDR_EVENT EventData)
|
||||
{
|
||||
KLOCK_QUEUE_HANDLE irpHandle;
|
||||
KLOCK_QUEUE_HANDLE qHandle;
|
||||
PIRP pendingIrp = NULL;
|
||||
|
||||
KeAcquireInStackQueuedSpinLock(&g_State.IrpLock, &irpHandle);
|
||||
|
||||
if (g_State.PendingIrp != NULL) {
|
||||
PIRP irp = g_State.PendingIrp;
|
||||
|
||||
if (IoSetCancelRoutine(irp, NULL) != NULL) {
|
||||
pendingIrp = irp;
|
||||
g_State.PendingIrp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
KeReleaseInStackQueuedSpinLock(&irpHandle);
|
||||
|
||||
if (pendingIrp != NULL) {
|
||||
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pendingIrp);
|
||||
ULONG outLen = stack->Parameters.DeviceIoControl.OutputBufferLength;
|
||||
|
||||
if (outLen >= sizeof(EDR_EVENT)) {
|
||||
RtlCopyMemory(
|
||||
pendingIrp->AssociatedIrp.SystemBuffer,
|
||||
EventData,
|
||||
sizeof(EDR_EVENT));
|
||||
pendingIrp->IoStatus.Information = sizeof(EDR_EVENT);
|
||||
pendingIrp->IoStatus.Status = STATUS_SUCCESS;
|
||||
}
|
||||
else {
|
||||
pendingIrp->IoStatus.Information = 0;
|
||||
pendingIrp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
IoCompleteRequest(pendingIrp, IO_NO_INCREMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
PEVENT_ENTRY entry = (PEVENT_ENTRY)ExAllocatePool2(
|
||||
POOL_FLAG_NON_PAGED, sizeof(EVENT_ENTRY), 'rvdE');
|
||||
if (entry == NULL) {
|
||||
DbgPrint("[EDR] EnqueueEvent: allocation failed, dropping event\n");
|
||||
return;
|
||||
}
|
||||
RtlCopyMemory(&entry->Event, EventData, sizeof(EDR_EVENT));
|
||||
|
||||
KeAcquireInStackQueuedSpinLock(&g_State.QueueLock, &qHandle);
|
||||
InsertTailList(&g_State.EventQueue, &entry->ListEntry);
|
||||
KeReleaseInStackQueuedSpinLock(&qHandle);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// IRP cancel routine
|
||||
// -------------------------------------------------------
|
||||
|
||||
static VOID CancelPendingIrp(
|
||||
_In_ PDEVICE_OBJECT DeviceObject,
|
||||
_In_ PIRP Irp)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(DeviceObject);
|
||||
|
||||
KLOCK_QUEUE_HANDLE irpHandle;
|
||||
KeAcquireInStackQueuedSpinLockAtDpcLevel(&g_State.IrpLock, &irpHandle);
|
||||
|
||||
if (g_State.PendingIrp == Irp) {
|
||||
g_State.PendingIrp = NULL;
|
||||
}
|
||||
|
||||
KeReleaseInStackQueuedSpinLockFromDpcLevel(&irpHandle);
|
||||
|
||||
IoReleaseCancelSpinLock(Irp->CancelIrql);
|
||||
|
||||
Irp->IoStatus.Status = STATUS_CANCELLED;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// LSASS Protection Callback (ObRegisterCallbacks)
|
||||
// -------------------------------------------------------
|
||||
|
||||
OB_PREOP_CALLBACK_STATUS PreProcessHandleCallback(
|
||||
PVOID RegistrationContext,
|
||||
POB_PRE_OPERATION_INFORMATION OperationInformation
|
||||
)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(RegistrationContext);
|
||||
|
||||
if (OperationInformation->ObjectType != *PsProcessType) {
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
PEPROCESS TargetProcess = (PEPROCESS)OperationInformation->Object;
|
||||
if (!TargetProcess) return OB_PREOP_SUCCESS;
|
||||
|
||||
PUNICODE_STRING ProcessImageName = NULL;
|
||||
NTSTATUS status = SeLocateProcessImageName(TargetProcess, &ProcessImageName);
|
||||
|
||||
if (NT_SUCCESS(status) && ProcessImageName && ProcessImageName->Buffer) {
|
||||
if (wcsstr(ProcessImageName->Buffer, L"lsass.exe") != NULL) {
|
||||
ACCESS_MASK OriginalAccess = OperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess;
|
||||
ACCESS_MASK NewAccess = OriginalAccess & ~(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION);
|
||||
|
||||
if (OriginalAccess != NewAccess) {
|
||||
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess = NewAccess;
|
||||
|
||||
DbgPrint("[EDR-LSASS] Stripped VM_READ|QUERY from lsass.exe handle. Original=0x%X New=0x%X\n",
|
||||
OriginalAccess, NewAccess);
|
||||
|
||||
EDR_EVENT ev = {};
|
||||
KeQuerySystemTime(&ev.Timestamp);
|
||||
ev.EventType = EVENT_TYPE_LSASS_ACCESS;
|
||||
ev.ProcessId = (ULONG64)PsGetCurrentProcessId();
|
||||
ev.Blocked = TRUE;
|
||||
SafeCopyUnicode(ev.ImageFileName, 260, ProcessImageName);
|
||||
EnqueueEvent(&ev);
|
||||
}
|
||||
}
|
||||
ExFreePool(ProcessImageName);
|
||||
}
|
||||
|
||||
return OB_PREOP_SUCCESS;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// DispatchIoControl
|
||||
// -------------------------------------------------------
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS DispatchIoControl(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
PIRP Irp)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(DeviceObject);
|
||||
|
||||
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
|
||||
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
|
||||
ULONG ioctl = stack->Parameters.DeviceIoControl.IoControlCode;
|
||||
|
||||
if (ioctl == IOCTL_KILL_PROCESS) {
|
||||
ULONG inLen = stack->Parameters.DeviceIoControl.InputBufferLength;
|
||||
|
||||
if (inLen < sizeof(EDR_COMMAND)) {
|
||||
status = STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
else {
|
||||
PEDR_COMMAND cmd = (PEDR_COMMAND)Irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
if (cmd->Action == 1) {
|
||||
PEPROCESS process = NULL;
|
||||
status = PsLookupProcessByProcessId((HANDLE)cmd->ProcessId, &process);
|
||||
|
||||
if (NT_SUCCESS(status)) {
|
||||
HANDLE hProcess = NULL;
|
||||
status = ObOpenObjectByPointer(
|
||||
process,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
NULL,
|
||||
PROCESS_TERMINATE,
|
||||
*PsProcessType,
|
||||
KernelMode,
|
||||
&hProcess);
|
||||
|
||||
if (NT_SUCCESS(status)) {
|
||||
status = ZwTerminateProcess(hProcess, STATUS_KILLED);
|
||||
ZwClose(hProcess);
|
||||
DbgPrint("[EDR] Killed process %llu (status: 0x%08X)\n",
|
||||
cmd->ProcessId, status);
|
||||
}
|
||||
|
||||
ObDereferenceObject(process);
|
||||
}
|
||||
else {
|
||||
DbgPrint("[EDR] Failed to find process %llu (status: 0x%08X)\n",
|
||||
cmd->ProcessId, status);
|
||||
}
|
||||
}
|
||||
else {
|
||||
status = STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
Irp->IoStatus.Status = status;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (ioctl == IOCTL_ADD_BLOCK_RULE) {
|
||||
ULONG inLen = stack->Parameters.DeviceIoControl.InputBufferLength;
|
||||
|
||||
if (inLen < sizeof(BLOCK_RULE_ENTRY)) {
|
||||
status = STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
else {
|
||||
PBLOCK_RULE_ENTRY rule = (PBLOCK_RULE_ENTRY)Irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
KLOCK_QUEUE_HANDLE ruleHandle;
|
||||
KeAcquireInStackQueuedSpinLock(&g_BlockRules.Lock, &ruleHandle);
|
||||
if (g_BlockRules.Count < BLOCK_RULE_MAX_ENTRIES) {
|
||||
RtlCopyMemory(&g_BlockRules.Rules[g_BlockRules.Count],
|
||||
rule, sizeof(BLOCK_RULE_ENTRY));
|
||||
g_BlockRules.Count++;
|
||||
status = STATUS_SUCCESS;
|
||||
DbgPrint("[EDR-BLOCK] Rule added (total: %lu)\n", g_BlockRules.Count);
|
||||
}
|
||||
else {
|
||||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
KeReleaseInStackQueuedSpinLock(&ruleHandle);
|
||||
}
|
||||
|
||||
Irp->IoStatus.Status = status;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (ioctl == IOCTL_CLEAR_BLOCK_RULES) {
|
||||
KLOCK_QUEUE_HANDLE ruleHandle;
|
||||
KeAcquireInStackQueuedSpinLock(&g_BlockRules.Lock, &ruleHandle);
|
||||
RtlZeroMemory(g_BlockRules.Rules,
|
||||
sizeof(BLOCK_RULE_ENTRY) * g_BlockRules.Count);
|
||||
g_BlockRules.Count = 0;
|
||||
KeReleaseInStackQueuedSpinLock(&ruleHandle);
|
||||
status = STATUS_SUCCESS;
|
||||
DbgPrint("[EDR-BLOCK] All rules cleared\n");
|
||||
|
||||
Irp->IoStatus.Status = status;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (ioctl == IOCTL_SIGNAL_LSASS_DUMP) {
|
||||
ULONG inLen = stack->Parameters.DeviceIoControl.InputBufferLength;
|
||||
|
||||
if (inLen < sizeof(EDR_COMMAND)) {
|
||||
status = STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
else {
|
||||
PEDR_COMMAND cmd = (PEDR_COMMAND)Irp->AssociatedIrp.SystemBuffer;
|
||||
|
||||
DbgPrint("[EDR-LSASS] Signal received from usermode - LSASS dump attempt by PID %llu\n",
|
||||
cmd->ProcessId);
|
||||
|
||||
if (cmd->Action == 1) {
|
||||
PEPROCESS process = NULL;
|
||||
NTSTATUS lookupStatus = PsLookupProcessByProcessId((HANDLE)cmd->ProcessId, &process);
|
||||
|
||||
if (NT_SUCCESS(lookupStatus)) {
|
||||
HANDLE hProcess = NULL;
|
||||
lookupStatus = ObOpenObjectByPointer(
|
||||
process,
|
||||
OBJ_KERNEL_HANDLE,
|
||||
NULL,
|
||||
PROCESS_TERMINATE,
|
||||
*PsProcessType,
|
||||
KernelMode,
|
||||
&hProcess);
|
||||
|
||||
if (NT_SUCCESS(lookupStatus)) {
|
||||
ZwTerminateProcess(hProcess, STATUS_KILLED);
|
||||
ZwClose(hProcess);
|
||||
DbgPrint("[EDR-LSASS] Terminated LSASS dumper PID %llu\n", cmd->ProcessId);
|
||||
}
|
||||
|
||||
ObDereferenceObject(process);
|
||||
}
|
||||
}
|
||||
|
||||
EDR_EVENT ev = {};
|
||||
KeQuerySystemTime(&ev.Timestamp);
|
||||
ev.EventType = EVENT_TYPE_LSASS_ACCESS;
|
||||
ev.ProcessId = cmd->ProcessId;
|
||||
ev.Blocked = TRUE;
|
||||
wcscpy_s(ev.ImageFileName, 260, L"LSASS_DUMP_BLOCKED");
|
||||
EnqueueEvent(&ev);
|
||||
|
||||
status = STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
Irp->IoStatus.Status = status;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (ioctl != IOCTL_WAIT_FOR_EVENT) {
|
||||
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return STATUS_INVALID_DEVICE_REQUEST;
|
||||
}
|
||||
|
||||
ULONG outLen = stack->Parameters.DeviceIoControl.OutputBufferLength;
|
||||
if (outLen < sizeof(EDR_EVENT)) {
|
||||
Irp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
KLOCK_QUEUE_HANDLE qHandle;
|
||||
KeAcquireInStackQueuedSpinLock(&g_State.QueueLock, &qHandle);
|
||||
|
||||
if (!IsListEmpty(&g_State.EventQueue)) {
|
||||
PLIST_ENTRY entry_link = RemoveHeadList(&g_State.EventQueue);
|
||||
PEVENT_ENTRY entry = CONTAINING_RECORD(entry_link, EVENT_ENTRY, ListEntry);
|
||||
KeReleaseInStackQueuedSpinLock(&qHandle);
|
||||
|
||||
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, &entry->Event, sizeof(EDR_EVENT));
|
||||
ExFreePool(entry);
|
||||
|
||||
Irp->IoStatus.Status = STATUS_SUCCESS;
|
||||
Irp->IoStatus.Information = sizeof(EDR_EVENT);
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
KeReleaseInStackQueuedSpinLock(&qHandle);
|
||||
|
||||
KLOCK_QUEUE_HANDLE irpHandle;
|
||||
KeAcquireInStackQueuedSpinLock(&g_State.IrpLock, &irpHandle);
|
||||
|
||||
if (g_State.PendingIrp != NULL) {
|
||||
KeReleaseInStackQueuedSpinLock(&irpHandle);
|
||||
Irp->IoStatus.Status = STATUS_DEVICE_BUSY;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return STATUS_DEVICE_BUSY;
|
||||
}
|
||||
|
||||
IoMarkIrpPending(Irp);
|
||||
IoSetCancelRoutine(Irp, CancelPendingIrp);
|
||||
|
||||
if (Irp->Cancel) {
|
||||
if (IoSetCancelRoutine(Irp, NULL) != NULL) {
|
||||
KeReleaseInStackQueuedSpinLock(&irpHandle);
|
||||
Irp->IoStatus.Status = STATUS_CANCELLED;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return STATUS_CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
g_State.PendingIrp = Irp;
|
||||
KeReleaseInStackQueuedSpinLock(&irpHandle);
|
||||
|
||||
return STATUS_PENDING;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// DispatchCreateClose
|
||||
// -------------------------------------------------------
|
||||
|
||||
_Use_decl_annotations_
|
||||
NTSTATUS DispatchCreateClose(
|
||||
PDEVICE_OBJECT DeviceObject,
|
||||
PIRP Irp)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(DeviceObject);
|
||||
Irp->IoStatus.Status = STATUS_SUCCESS;
|
||||
Irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Thread callback
|
||||
// -------------------------------------------------------
|
||||
|
||||
VOID ThreadCallback(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create)
|
||||
{
|
||||
EDR_EVENT ev = {};
|
||||
KeQuerySystemTime(&ev.Timestamp);
|
||||
ev.EventType = Create ? EVENT_TYPE_THREAD_CREATE : EVENT_TYPE_THREAD_EXIT;
|
||||
ev.ProcessId = (ULONG64)ProcessId;
|
||||
ev.ThreadId = (ULONG64)ThreadId;
|
||||
EnqueueEvent(&ev);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Block rule matching helpers
|
||||
// -------------------------------------------------------
|
||||
|
||||
static BOOLEAN WcsEndsWithInsensitive(
|
||||
_In_ PWCHAR Str, _In_ ULONG StrLen, _In_ PCWSTR Suf)
|
||||
{
|
||||
SIZE_T sufLen = wcslen(Suf);
|
||||
if (!sufLen || StrLen < sufLen) return FALSE;
|
||||
SIZE_T start = StrLen - sufLen;
|
||||
for (SIZE_T i = 0; i < sufLen; ++i)
|
||||
if (RtlUpcaseUnicodeChar(Str[start + i]) !=
|
||||
RtlUpcaseUnicodeChar(Suf[i])) return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOLEAN WcsContainsInsensitive(
|
||||
_In_ PWCHAR Str, _In_ ULONG StrLen, _In_ PCWSTR Sub)
|
||||
{
|
||||
SIZE_T subLen = wcslen(Sub);
|
||||
if (!subLen || StrLen < subLen) return FALSE;
|
||||
for (SIZE_T pos = 0; pos <= StrLen - subLen; ++pos) {
|
||||
BOOLEAN match = TRUE;
|
||||
for (SIZE_T i = 0; i < subLen; ++i)
|
||||
if (RtlUpcaseUnicodeChar(Str[pos + i]) !=
|
||||
RtlUpcaseUnicodeChar(Sub[i])) { match = FALSE; break; }
|
||||
if (match) return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOLEAN MatchBlockRule(
|
||||
_In_ PBLOCK_RULE_ENTRY Rule,
|
||||
_In_ PWCHAR ImageFileName,
|
||||
_In_ ULONG ImageLen,
|
||||
_In_ PWCHAR CmdLine,
|
||||
_In_ ULONG CmdLineLen)
|
||||
{
|
||||
BOOLEAN imgOk = TRUE;
|
||||
if (Rule->ImageSuffix[0] != L'\0')
|
||||
imgOk = WcsEndsWithInsensitive(ImageFileName, ImageLen, Rule->ImageSuffix);
|
||||
|
||||
BOOLEAN cmdOk = TRUE;
|
||||
if (Rule->CmdLineSubstr[0] != L'\0')
|
||||
cmdOk = WcsContainsInsensitive(CmdLine, CmdLineLen, Rule->CmdLineSubstr);
|
||||
|
||||
return imgOk && cmdOk;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Safe unicode copy
|
||||
// -------------------------------------------------------
|
||||
|
||||
static VOID SafeCopyUnicode(
|
||||
_Out_writes_z_(dstMax) PWCHAR dst,
|
||||
_In_ SIZE_T dstMax,
|
||||
_In_ PCUNICODE_STRING src)
|
||||
{
|
||||
if (!src || !src->Buffer || src->Length == 0) { dst[0] = L'\0'; return; }
|
||||
SIZE_T chars = min(src->Length / sizeof(WCHAR), dstMax - 1);
|
||||
RtlCopyMemory(dst, src->Buffer, chars * sizeof(WCHAR));
|
||||
dst[chars] = L'\0';
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// Process callback
|
||||
// -------------------------------------------------------
|
||||
|
||||
VOID ProcessCallback(
|
||||
PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(Process);
|
||||
|
||||
EDR_EVENT ev = {};
|
||||
KeQuerySystemTime(&ev.Timestamp);
|
||||
ev.ProcessId = (ULONG64)ProcessId;
|
||||
|
||||
if (CreateInfo != NULL) {
|
||||
ev.EventType = EVENT_TYPE_PROCESS_CREATE;
|
||||
ev.ParentProcessId = (ULONG64)CreateInfo->ParentProcessId;
|
||||
|
||||
if (CreateInfo->ImageFileName)
|
||||
SafeCopyUnicode(ev.ImageFileName, 260, CreateInfo->ImageFileName);
|
||||
if (CreateInfo->CommandLine)
|
||||
SafeCopyUnicode(ev.CommandLine, 512, CreateInfo->CommandLine);
|
||||
|
||||
ULONG imgLen = (ULONG)wcslen(ev.ImageFileName);
|
||||
ULONG cmdLen = (ULONG)wcslen(ev.CommandLine);
|
||||
|
||||
KLOCK_QUEUE_HANDLE ruleHandle;
|
||||
KeAcquireInStackQueuedSpinLock(&g_BlockRules.Lock, &ruleHandle);
|
||||
for (ULONG i = 0; i < g_BlockRules.Count; ++i) {
|
||||
if (MatchBlockRule(&g_BlockRules.Rules[i],
|
||||
ev.ImageFileName, imgLen, ev.CommandLine, cmdLen))
|
||||
{
|
||||
CreateInfo->CreationStatus = STATUS_ACCESS_DENIED;
|
||||
ev.Blocked = TRUE;
|
||||
DbgPrint("[EDR-BLOCK] Blocked PID %llu per rule idx %lu\n",
|
||||
ev.ProcessId, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
KeReleaseInStackQueuedSpinLock(&ruleHandle);
|
||||
}
|
||||
else {
|
||||
ev.EventType = EVENT_TYPE_PROCESS_EXIT;
|
||||
}
|
||||
|
||||
EnqueueEvent(&ev);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// DriverUnload
|
||||
// -------------------------------------------------------
|
||||
|
||||
VOID DriverUnload(_In_ PDRIVER_OBJECT DriverObject)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(DriverObject);
|
||||
|
||||
if (g_ObRegistrationHandle) {
|
||||
ObUnRegisterCallbacks(g_ObRegistrationHandle);
|
||||
g_ObRegistrationHandle = NULL;
|
||||
}
|
||||
|
||||
NTSTATUS status;
|
||||
status = PsRemoveCreateThreadNotifyRoutine(ThreadCallback);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
DbgPrint("[EDR] Failed to remove thread callback: %08X\n", status);
|
||||
}
|
||||
|
||||
status = PsSetCreateProcessNotifyRoutineEx(ProcessCallback, TRUE);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
DbgPrint("[EDR] Failed to remove process callback: %08X\n", status);
|
||||
}
|
||||
|
||||
KLOCK_QUEUE_HANDLE qHandle;
|
||||
KeAcquireInStackQueuedSpinLock(&g_State.QueueLock, &qHandle);
|
||||
while (!IsListEmpty(&g_State.EventQueue)) {
|
||||
PLIST_ENTRY link = RemoveHeadList(&g_State.EventQueue);
|
||||
PEVENT_ENTRY entry = CONTAINING_RECORD(link, EVENT_ENTRY, ListEntry);
|
||||
ExFreePool(entry);
|
||||
}
|
||||
KeReleaseInStackQueuedSpinLock(&qHandle);
|
||||
|
||||
KLOCK_QUEUE_HANDLE irpHandle;
|
||||
KeAcquireInStackQueuedSpinLock(&g_State.IrpLock, &irpHandle);
|
||||
PIRP irp = g_State.PendingIrp;
|
||||
g_State.PendingIrp = NULL;
|
||||
KeReleaseInStackQueuedSpinLock(&irpHandle);
|
||||
|
||||
if (irp != NULL) {
|
||||
if (IoSetCancelRoutine(irp, NULL) != NULL) {
|
||||
irp->IoStatus.Status = STATUS_CANCELLED;
|
||||
irp->IoStatus.Information = 0;
|
||||
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
||||
}
|
||||
}
|
||||
|
||||
UNICODE_STRING symlink = RTL_CONSTANT_STRING(L"\\??\\EDRTEST");
|
||||
IoDeleteSymbolicLink(&symlink);
|
||||
if (g_DeviceObject) IoDeleteDevice(g_DeviceObject);
|
||||
|
||||
DbgPrint("[EDR] Driver unloaded\n");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// DriverEntry
|
||||
// -------------------------------------------------------
|
||||
|
||||
extern "C"
|
||||
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(RegistryPath);
|
||||
|
||||
DriverObject->DriverUnload = DriverUnload;
|
||||
|
||||
UNICODE_STRING devname = RTL_CONSTANT_STRING(L"\\Device\\EDRTEST");
|
||||
UNICODE_STRING symlink = RTL_CONSTANT_STRING(L"\\??\\EDRTEST");
|
||||
|
||||
NTSTATUS status = IoCreateDevice(
|
||||
DriverObject, 0, &devname,
|
||||
FILE_DEVICE_UNKNOWN, 0, FALSE, &g_DeviceObject);
|
||||
if (!NT_SUCCESS(status)) return status;
|
||||
|
||||
g_DeviceObject->Flags |= DO_BUFFERED_IO;
|
||||
|
||||
status = IoCreateSymbolicLink(&symlink, &devname);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
IoDeleteDevice(g_DeviceObject);
|
||||
return status;
|
||||
}
|
||||
|
||||
InitializeListHead(&g_State.EventQueue);
|
||||
KeInitializeSpinLock(&g_State.QueueLock);
|
||||
KeInitializeSpinLock(&g_State.IrpLock);
|
||||
KeInitializeSpinLock(&g_BlockRules.Lock);
|
||||
g_State.PendingIrp = NULL;
|
||||
|
||||
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoControl;
|
||||
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
|
||||
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
|
||||
|
||||
OB_CALLBACK_REGISTRATION CallbackRegistration = { 0 };
|
||||
OB_OPERATION_REGISTRATION OperationRegistration = { 0 };
|
||||
|
||||
OperationRegistration.ObjectType = PsProcessType;
|
||||
OperationRegistration.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
|
||||
OperationRegistration.PreOperation = PreProcessHandleCallback;
|
||||
OperationRegistration.PostOperation = NULL;
|
||||
|
||||
CallbackRegistration.Version = OB_FLT_REGISTRATION_VERSION;
|
||||
CallbackRegistration.OperationRegistrationCount = 1;
|
||||
CallbackRegistration.RegistrationContext = NULL;
|
||||
CallbackRegistration.OperationRegistration = &OperationRegistration;
|
||||
|
||||
DECLARE_CONST_UNICODE_STRING(Altitude, L"370000");
|
||||
CallbackRegistration.Altitude = Altitude;
|
||||
|
||||
status = ObRegisterCallbacks(&CallbackRegistration, &g_ObRegistrationHandle);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
DbgPrint("[EDR] ObRegisterCallbacks failed: 0x%08X\n", status);
|
||||
g_ObRegistrationHandle = NULL;
|
||||
}
|
||||
else {
|
||||
DbgPrint("[EDR] LSASS protection callback registered\n");
|
||||
}
|
||||
|
||||
status = PsSetCreateProcessNotifyRoutineEx(ProcessCallback, FALSE);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
if (g_ObRegistrationHandle) ObUnRegisterCallbacks(g_ObRegistrationHandle);
|
||||
IoDeleteSymbolicLink(&symlink);
|
||||
IoDeleteDevice(g_DeviceObject);
|
||||
return status;
|
||||
}
|
||||
|
||||
status = PsSetCreateThreadNotifyRoutine(ThreadCallback);
|
||||
if (!NT_SUCCESS(status)) {
|
||||
PsSetCreateProcessNotifyRoutineEx(ProcessCallback, TRUE);
|
||||
if (g_ObRegistrationHandle) ObUnRegisterCallbacks(g_ObRegistrationHandle);
|
||||
IoDeleteSymbolicLink(&symlink);
|
||||
IoDeleteDevice(g_DeviceObject);
|
||||
return status;
|
||||
}
|
||||
|
||||
DbgPrint("[EDR] Driver loaded successfully with LSASS protection\n");
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user