进程创建通知回调通知例程的学习笔记
2017-08-31 21:16
381 查看
转自:小刀志
在 Windows 操作系统中可以通过 PsSetCreateProcessNotifyRoutine 函数注册或移除一个进程创建通知回调例程。在 Vista 以及之后的版本中,微软加入 PsSetCreateProcessNotifyRoutineEx 新的函数来注册创建进程通知。通过判断系统版本来对应不同的操作系统调用不同的注册函数。
而在 Vista 之前的系统版本(如 Windows XP)中由于没有 PsSetCreateProcessNotifyRoutineEx 这个函数,会驱动加载的时候导致加载失败。那么通过 MmGetSystemRoutineAddress 函数可以动态地获取 PsSetCreateProcessNotifyRoutineEx 函数。
是通过参数中指向 PS_CREATE_NOTIFY_INFO 类型的结构体指针 PPS_CREATE_NOTIFY_INFO CreateInfo 来进行判断。
typedef struct _PS_CREATE_NOTIFY_INFO {
SIZE_T Size;
union {
ULONG Flags;
struct {
ULONG FileOpenNameAvailable :1;
ULONG Reserved :31;
};
};
HANDLE ParentProcessId;
CLIENT_ID CreatingThreadId;
struct _FILE_OBJECT *FileObject;
PCUNICODE_STRING ImageFileName;
PCUNICODE_STRING CommandLine;
NTSTATUS CreationStatus;
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
如果为创建进程,则该参数指针指向该结构体的一个结构体对象,可通过该对象获得线程ID、父进程ID、文件对象、映像文件名、命令行字符串等进程信息;而如果是销毁进程,则该参数指针指向 NULL。
编译驱动程序并在虚拟机系统中进行调试。调试过程中发现 PsSetCreateProcessNotifyRoutineEx 调用失败,返回值为 STATUS_ACCESS_DENIED 即 0xC00000CC 错误码。WDK 解释为是由于生成的驱动程序文件 PE 头中没有被设置 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 标志导致。
操作系统通过内核函数 MmVerifyCallbackFunction 对加载完成的驱动进行完整性校验标志位的检测,该标志位未被置位的驱动模块会被禁止使用某些函数,如上面提到的 PsSetCreateProcessNotifyRoutineEx。
通过反汇编发现,在 PsSetCreateProcessNotifyRoutineEx 中调用 PspSetCreateProcessNotifyRoutine 函数,在其中通过调用 MmVerifyCallbackFunction 对驱动的完整性校验标志位进行检查,检查失败时则会使当前函数返回 0xC00000CC 错误码。
解决方法是在 sources 文件中加入一行:LINKER_FLAGS=/INTEGRITYCHECK 以开启驱动程序的完整性校验,这个方法适用于通过 WDK 编译器编译环境进行编译的情况。如果是通过 VisualStudio 自身编译器作为交叉编译工具链,则需在“项目-属性-链接器-命令行”位置添加 /INTEGRITYCHECK 即可。
这时候再进行测试运行,会发现在 Windows 7 的非测试模式的环境下驱动程序会加载失败。查阅资料发现是由于 INTEGRITYCHECK 标志导致强制进行驱动签名校验的问题;而在测试模式系统环境下不进行签名校验,所以会成功加载。
在 32 位版本的 Windows 7 环境中,驱动程序加载时操作系统根据 PE 文件头部对应的 Flags 域的值判断是否置位 INTEGRITYCHECK 标志位,并根据判断的结果来决定是否要进行代码签名校验操作,在这里签名校验操作并非强制性的。
然而需要注意的是,在 64 位版 Windows 7 系统中,驱动程序加载时的安全性检查机制有所不同。微软为 Windows Vista 及后续版本的操作系统的 x64 位版本加强了驱动程序的安全性校验机制,编译生成的驱动程序文件的 PE 头部对应的 Flags 标志位无论是否已置位 INTEGRITYCHECK(0x20) 标志位,在驱动程序加载时都会执行签名校验的操作。
所以在 64 位版本的操作系统中的非测试模式或调试模式环境下,如果需要加载编译生成的驱动程序,那么一定需要通过代码签名证书对驱动程序进行交叉签名。
目前的问题是:
1. 如果将驱动文件的 INTEGRITYCHECK 标志位置位,驱动加载的时候会强制对文件签名进行校验,无签名或签名无效的驱动会被禁止加载.
2. 而如果不将 INTEGRITYCHECK 标志位置位,MmVerifyCallbackFunction 校验函数会造成部分内核函数的调用失败。
这样的话就需要找到一种既能使驱动成功加载、又能绕过完整性校验标志位检测的方法。
首先,移除上文中在 source 文件中添加的 LINKER_FLAGS=/INTEGRITYCHECK 这行,然后将以下代码放置在 DriverEntry 函数中。
PLDR_DATA_TABLE_ENTRY ldr;
ldr = (PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection;
ldr->Flags |= 0x20;这里的
PLDR_DATA_TABLE_ENTRY 需要自行定义,根据系统版本和位数的不同可能会有差异。在实际应用中可以使用以下定义:
typedef struct _LDR_DATA // 24 elements, 0xE0 bytes (sizeof)
{
struct _LIST_ENTRY InLoadOrderLinks; // 2 elements, 0x10 bytes (sizeof)
struct _LIST_ENTRY InMemoryOrderLinks; // 2 elements, 0x10 bytes (sizeof)
struct _LIST_ENTRY InInitializationOrderLinks; // 2 elements, 0x10 bytes (sizeof)
VOID* DllBase;
VOID* EntryPoint;
ULONG32 SizeOfImage;
#ifdef _WIN64
UINT8 _PADDING0_[0x4];
#endif
struct _UNICODE_STRING FullDllName; // 3 elements, 0x10 bytes (sizeof)
struct _UNICODE_STRING BaseDllName; // 3 elements, 0x10 bytes (sizeof)
ULONG32 Flags;
}LDR_DATA, *PLDR_DATA;
至于该结构体的完整定义可通过 WinDbg 命令 dt nt!_LDR_DATA_TABLE_ENTRY 在对应的系统中查看。
至于 MmVerifyCallbackFunction 具体执行了那些操作,我们该怎么绕过其驱动程序强制签名校验?在下一篇文章中将尝试做一个简单的分析。
在 Windows 操作系统中可以通过 PsSetCreateProcessNotifyRoutine 函数注册或移除一个进程创建通知回调例程。在 Vista 以及之后的版本中,微软加入 PsSetCreateProcessNotifyRoutineEx 新的函数来注册创建进程通知。通过判断系统版本来对应不同的操作系统调用不同的注册函数。
而在 Vista 之前的系统版本(如 Windows XP)中由于没有 PsSetCreateProcessNotifyRoutineEx 这个函数,会驱动加载的时候导致加载失败。那么通过 MmGetSystemRoutineAddress 函数可以动态地获取 PsSetCreateProcessNotifyRoutineEx 函数。
typedef NTSTATUS (_stdcall *PPsSetCreateProcessNotifyRoutineEx)( IN PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, IN BOOLEAN Remove ); PPsSetCreateProcessNotifyRoutineEx g_pPsSetCreateProcessNotifyRoutineEx = NULL; BOOLEAN g_bSucReg = FALSE; BOOLEAN g_bUsedEx = FALSE; NTSTATUS SetProcessCallBack () { NTSTATUS nStatus = STATUS_UNSUCCESSFUL; do { UNICODE_STRING uFuncName = {0}; RtlInitUnicodeString(&uFuncName,L"PsSetCreateProcessNotifyRoutineEx"); g_pPsSetCreateProcessNotifyRoutineEx = (PPsSetCreateProcessNotifyRoutineEx)MmGetSystemRoutineAddress(&uFuncName); if( g_pPsSetCreateProcessNotifyRoutineEx == NULL ) { break; } if( STATUS_SUCCESS != g_pPsSetCreateProcessNotifyRoutineEx(CreateProcessNotifyEx,FALSE) ) { break; } g_bSucReg = TRUE; g_bUsedEx = TRUE; nStatus = STATUS_SUCCESS; } while (FALSE); do { if ( TRUE == g_bUsedEx ) { break; } if( STATUS_SUCCESS != PsSetCreateProcessNotifyRoutine(CreateProcessNotify,FALSE) ) { break; } g_bSucReg = TRUE; g_bUsedEx = FALSE; nStatus = STATUS_SUCCESS; } while (FALSE); return nStatus; }通知例程处理函数也需要同时配套地使用新的 CreateProcessNotifyEx 函数定义格式和函数体。与旧版本 CreateProcessNotify 通过 BOOLEAN Create 参数判断是创建还是销毁进程不同的是,CreateProcessNotifyEx
是通过参数中指向 PS_CREATE_NOTIFY_INFO 类型的结构体指针 PPS_CREATE_NOTIFY_INFO CreateInfo 来进行判断。
VOID CreateProcessNotifyEx ( __inout PEPROCESS Process, __in HANDLE ProcessId, __in_opt PPS_CREATE_NOTIFY_INFO CreateInfo ) { HANDLE hCurrentThreadID = NULL; hCurrentThreadID = PsGetCurrentThreadId(); if( CreateInfo == NULL ) { DbgPrint("进程销毁: %08X %08X\n", ProcessId, hCurrentThreadID); return; } DbgPrint("进程创建: %08X %08X\n", ProcessId, hCurrentThreadID); return; } VOID CreateProcessNotify ( IN HANDLE ParentId, IN HANDLE ProcessId, IN BOOLEAN Create ) { HANDLE hCurrentThreadID = NULL; hCurrentThreadID = PsGetCurrentThreadId(); if( !Create ) { DbgPrint("进程销毁: %08X %08X\n", ProcessId, hCurrentThreadID); return; } DbgPrint("进程创建: %08X %08X\n", ProcessId, hCurrentThreadID); return; }PS_CREATE_NOTIFY_INFO 结构体类型定义如下:
typedef struct _PS_CREATE_NOTIFY_INFO {
SIZE_T Size;
union {
ULONG Flags;
struct {
ULONG FileOpenNameAvailable :1;
ULONG Reserved :31;
};
};
HANDLE ParentProcessId;
CLIENT_ID CreatingThreadId;
struct _FILE_OBJECT *FileObject;
PCUNICODE_STRING ImageFileName;
PCUNICODE_STRING CommandLine;
NTSTATUS CreationStatus;
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
如果为创建进程,则该参数指针指向该结构体的一个结构体对象,可通过该对象获得线程ID、父进程ID、文件对象、映像文件名、命令行字符串等进程信息;而如果是销毁进程,则该参数指针指向 NULL。
编译驱动程序并在虚拟机系统中进行调试。调试过程中发现 PsSetCreateProcessNotifyRoutineEx 调用失败,返回值为 STATUS_ACCESS_DENIED 即 0xC00000CC 错误码。WDK 解释为是由于生成的驱动程序文件 PE 头中没有被设置 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 标志导致。
操作系统通过内核函数 MmVerifyCallbackFunction 对加载完成的驱动进行完整性校验标志位的检测,该标志位未被置位的驱动模块会被禁止使用某些函数,如上面提到的 PsSetCreateProcessNotifyRoutineEx。
通过反汇编发现,在 PsSetCreateProcessNotifyRoutineEx 中调用 PspSetCreateProcessNotifyRoutine 函数,在其中通过调用 MmVerifyCallbackFunction 对驱动的完整性校验标志位进行检查,检查失败时则会使当前函数返回 0xC00000CC 错误码。
解决方法是在 sources 文件中加入一行:LINKER_FLAGS=/INTEGRITYCHECK 以开启驱动程序的完整性校验,这个方法适用于通过 WDK 编译器编译环境进行编译的情况。如果是通过 VisualStudio 自身编译器作为交叉编译工具链,则需在“项目-属性-链接器-命令行”位置添加 /INTEGRITYCHECK 即可。
这时候再进行测试运行,会发现在 Windows 7 的非测试模式的环境下驱动程序会加载失败。查阅资料发现是由于 INTEGRITYCHECK 标志导致强制进行驱动签名校验的问题;而在测试模式系统环境下不进行签名校验,所以会成功加载。
在 32 位版本的 Windows 7 环境中,驱动程序加载时操作系统根据 PE 文件头部对应的 Flags 域的值判断是否置位 INTEGRITYCHECK 标志位,并根据判断的结果来决定是否要进行代码签名校验操作,在这里签名校验操作并非强制性的。
然而需要注意的是,在 64 位版 Windows 7 系统中,驱动程序加载时的安全性检查机制有所不同。微软为 Windows Vista 及后续版本的操作系统的 x64 位版本加强了驱动程序的安全性校验机制,编译生成的驱动程序文件的 PE 头部对应的 Flags 标志位无论是否已置位 INTEGRITYCHECK(0x20) 标志位,在驱动程序加载时都会执行签名校验的操作。
所以在 64 位版本的操作系统中的非测试模式或调试模式环境下,如果需要加载编译生成的驱动程序,那么一定需要通过代码签名证书对驱动程序进行交叉签名。
目前的问题是:
1. 如果将驱动文件的 INTEGRITYCHECK 标志位置位,驱动加载的时候会强制对文件签名进行校验,无签名或签名无效的驱动会被禁止加载.
2. 而如果不将 INTEGRITYCHECK 标志位置位,MmVerifyCallbackFunction 校验函数会造成部分内核函数的调用失败。
这样的话就需要找到一种既能使驱动成功加载、又能绕过完整性校验标志位检测的方法。
首先,移除上文中在 source 文件中添加的 LINKER_FLAGS=/INTEGRITYCHECK 这行,然后将以下代码放置在 DriverEntry 函数中。
PLDR_DATA_TABLE_ENTRY ldr;
ldr = (PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection;
ldr->Flags |= 0x20;这里的
PLDR_DATA_TABLE_ENTRY 需要自行定义,根据系统版本和位数的不同可能会有差异。在实际应用中可以使用以下定义:
typedef struct _LDR_DATA // 24 elements, 0xE0 bytes (sizeof)
{
struct _LIST_ENTRY InLoadOrderLinks; // 2 elements, 0x10 bytes (sizeof)
struct _LIST_ENTRY InMemoryOrderLinks; // 2 elements, 0x10 bytes (sizeof)
struct _LIST_ENTRY InInitializationOrderLinks; // 2 elements, 0x10 bytes (sizeof)
VOID* DllBase;
VOID* EntryPoint;
ULONG32 SizeOfImage;
#ifdef _WIN64
UINT8 _PADDING0_[0x4];
#endif
struct _UNICODE_STRING FullDllName; // 3 elements, 0x10 bytes (sizeof)
struct _UNICODE_STRING BaseDllName; // 3 elements, 0x10 bytes (sizeof)
ULONG32 Flags;
}LDR_DATA, *PLDR_DATA;
至于该结构体的完整定义可通过 WinDbg 命令 dt nt!_LDR_DATA_TABLE_ENTRY 在对应的系统中查看。
至于 MmVerifyCallbackFunction 具体执行了那些操作,我们该怎么绕过其驱动程序强制签名校验?在下一篇文章中将尝试做一个简单的分析。
相关文章推荐
- nginx 源码学习笔记(十六)—— ngx_start_worker_processes子进程创建
- 【Linux内核学习笔记】进程的创建过程
- 操作系统学习笔记(35)--创建新进程并调度
- nginx 源码学习笔记(十六)—— ngx_start_worker_processes子进程创建
- 线程学习笔记【1】----进程、线程概念及创建线程
- Linux进程线程学习笔记:进程创建
- Linux进程学习笔记(二、创建进程)
- LINUX编程学习笔记(十四) 创建进程与 父子进程内存空间
- LINUX编程学习笔记(十四) 创建进程与 父子进程内存空间
- Linux内核学习笔记之进程创建(十)
- WinApi学习笔记-创建进程
- 20135202闫佳歆--week6 进程的描述与创建--学习笔记
- Linux内核分析第六周学习笔记——分析Linux内核创建一个新进程的过程
- Linux进程线程学习笔记:进程创建
- Linux进程线程学习笔记:进程创建
- halcon例程学习笔记(5)----halcon中如何自己创建子过程
- JAVA学习笔记49——线程概念+进程、线程区别+创建进程三种方法
- Linux进程线程学习笔记:进程创建
- Silverlight学习笔记1:创建一个Silverlight应用程序
- JavaScript学习笔记 创建数组,数组方法使用