Windows操作系统通过BOOLEAN函数注册或一个进程创建通知回调例程

在Windows操作系统中可以通过PsSetCreateProcessNotifyRoutine函数注册或移除一个进程创建通知反弹类库。在Vista以及以后的版本中,谷歌加入PsSetCreateProcessNotifyRoutineEx新的函数来注册创建进程通知。通过判定系统版本来对应不同的操作系统调用不同的注册函数。

而在Vista之前的系统版本(如WindowsXP)中因为没有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;

图片[1]-Windows操作系统通过BOOLEAN函数注册或一个进程创建通知回调例程-唐朝资源网

} while (FALSE); return nStatus; }

通知类库处理函数也须要同时配套地使用新的CreateProcessNotifyEx函数定义格式和函数体。与旧版本CreateProcessNotify通过BOOLEANCreate参数判定是创建还是销毁进程不同的是,CreateProcessNotifyEx是通过参数中指向PS_CREATE_NOTIFY_INFO类型的结构体表针PPS_CREATE_NOTIFY_INFOCreateInfo来进行判定。

VOID 
CreateProcessNotifyEx (
                      __inout PEPROCESS  Process,
                      __in HANDLE  ProcessId,
                      __in_opt PPS_CREATE_NOTIFY_INFO  CreateInfo
                      )
{
    HANDLE hCurrentThreadID = NULL;
    hCurrentThreadID = PsGetCurrentThreadId();
    if( CreateInfo == NULL )
    {
        DbgPrint("进程销毁: X Xn", ProcessId, hCurrentThreadID);
        return;
    }
    DbgPrint("进程创建: X Xn", ProcessId, hCurrentThreadID);
    return;
}

VOID 
CreateProcessNotify (
                    IN HANDLE   ParentId,
                    IN HANDLE   ProcessId,
                    IN BOOLEAN  Create
                    )
{
    HANDLE hCurrentThreadID = NULL;
    hCurrentThreadID = PsGetCurrentThreadId();
    if( !Create )
    {
        DbgPrint("进程销毁: X Xn", ProcessId, hCurrentThreadID);
        return;
    }
    DbgPrint("进程创建: X Xn", 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即0xCxC00000CC错误码。WDK解释为是因为生成的驱动程序文件PE头中没有被设置IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY标志引起。

操作系统通过内核函数MmVerifyCallbackFunction对加载完成的驱动进行完整性校准标志位的检查,该标志位未被置位的驱动模块会被严禁使用个别函数,如前面提及的PsSetCreateProcessNotifyRoutineEx。

图片[2]-Windows操作系统通过BOOLEAN函数注册或一个进程创建通知回调例程-唐朝资源网

通过反汇编发觉,在PsSetCreateProcessNotifyRoutineEx中调用PspSetCreateProcessNotifyRoutine函数,在其中通过调用MmVerifyCallbackFunction对驱动的完整性校准标志位进行检测,检测失败时则会使当前函数返回0xCxC00000CC错误码。

解决方式是在sources文件中加入一行:LINKER_FLAGS=/INTEGRITYCHECK以开启驱动程序的完整性校准,这个方式适用于通过WDK编译器编译环境进行编译的情况。假如是通过VisualStudio自身编译器作为交叉编译工具链,则需在“项目-属性-链接器-命令行”位置添加/INTEGRITYCHECK即可。

这时侯再进行测试运行,会发觉在Windows7的非测试模式的环境下驱动程序会加载失败。查阅资料发觉是因为INTEGRITYCHECK标志引起强制进行驱动签名校准的问题;而在测试模式系统环境下不进行签名校准,所以会成功加载。

在32位版本的Windows7环境中,驱动程序加载时操作系统按照PE文件腹部对应的Flags域的值判别是否置位INTEGRITYCHECK标志位,并按照判定的结果来决定是否要进行代码签名校准操作,在这儿签名校准操作并非强制性的。

但是须要注意的是,在64位版Windows7系统中,驱动程序加载时的安全性检测机制有所不同。谷歌为WindowsVista及后续版本的操作系统的x64位版本强化了驱动程序的安全性校准机制,编译生成的驱动程序文件的PE背部对应的Flags标志位无论是否已置位INTEGRITYCHECK(0x20)标志位,在驱动程序加载时就会执行签名校准的操作。

所以在64位版本的操作系统中的非测试模式或调试模式环境下,假如须要加载编译生成的驱动程序,这么一定须要通过代码签名证书对驱动程序进行交叉签名。

目前的问题是:

1.假如将驱动文件的INTEGRITYCHECK标志位置位,驱动加载的时侯会强制对文件签名进行校准,无签名或签名无效的驱动会被严禁加载.

图片[3]-Windows操作系统通过BOOLEAN函数注册或一个进程创建通知回调例程-唐朝资源网

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命令dtnt!_LDR_DATA_TABLE_ENTRY在对应的系统中查看。

至于MmVerifyCallbackFunction具体执行了这些操作,我们该如何绕开其驱动程序强制签名校准?在下一篇文章上将尝试做一个简单的剖析。

© 版权声明
THE END
喜欢就支持一下吧
点赞206赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容