基于PsSetCreateProcessNotifyRoutineEx实现监控进程创建并阻止创建(禁用QQ 360等exe可执行文件)

对于内核层实现监控进程的创建或者退出,你可能第一时间会想到 HOOK 内核函数 ZwOpenProcess、ZwTerminateProcess 等。确定,在内核层中的 HOOK 已经给人留下太多深刻的印象了,有 SSDT HOOK、Inline HOOK、IRP HOOK、过滤驱动等等。

但是,Windows 其实给我们提供现成的内核函数接口,方便我们在内核下监控用户层上进程的创建和退出的情况。即 PsSetCreateProcessNotifyRoutineEx 内核函数,可以设置一个回调函数,来监控进程的创建和退出,同时还能控制是否允许创建进程。

现在,本文就使用 PsSetCreateProcessNotifyRoutineEx 实现监控进程的创建的实现过程和原理进行整理,形成文档,分享给大家。

1.PsSetCreateProcessNotifyRoutineEx 函数

设置进程回调监控进程创建与退出,而且还能控制是否允许进程创建

函数声明

NTSTATUS PsSetCreateProcessNotifyRoutineEx(
    _In_ PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
    _In_ BOOLEAN                           Remove
);

参数

  • NotifyRoutine [in]
    指向PCREATE_PROCESS_NOTIFY_ROUTINE_EX例程以注册或删除的指针。 创建新进程时,操作系统将调用此例程。
  • Remove[in]
    一个布尔值,指定PsSetCreateProcessNotifyRoutineEx是否会从回调例程列表中添加或删除指定的例程。 如果此参数为TRUE,则从回调例程列表中删除指定的例程。 如果此参数为FALSE,则将指定的例程添加到回调例程列表中。 如果删除为TRUE,系统还会等待所有正在运行的回调例程运行完成。

返回值

  • 成功,则返回 STATUS_SUCCESS;否则,返回其它失败错误码 NTSTATUS。

 

2.PCREATE_PROCESS_NOTIFY_ROUTINE_EX 回调函数

函数声明

PCREATE_PROCESS_NOTIFY_ROUTINE_EX SetCreateProcessNotifyRoutineEx;
void SetCreateProcessNotifyRoutineEx(
    _In_        HANDLE                 ParentId,
    _In_        HANDLE                 ProcessId,
    _Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo
)
{ ... }

参数

  • ParentId [in]
    父进程的进程ID。
  • ProcessId [in]
    进程的进程ID。
  • CreateInfo [in,out,optional]
    指向PS_CREATE_NOTIFY_INFO结构的指针,其中包含有关新进程的信息。为 NULL 时,表示进程退出;不为 NULL 时,表示进程创建。

返回值

  • 无返回值。

 

3.PS_CREATE_NOTIFY_INFO 结构体

typedef struct _PS_CREATE_NOTIFY_INFO {
  SIZE_T              Size;
  union {
    ULONG  Flags;
    struct {
      ULONG FileOpenNameAvailable  :1;
      ULONG IsSubsystemProcess   :1;
      ULONG Reserved  :30;
    };
  };
  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;

成员

  • Size
    该结构的大小(以字节为单位)。
  • Flags
    保留。 请改用FileOpenNameAvailable成员。
  • FileOpenNameAvailable
    一个布尔值,指定ImageFileName成员是否包含用于打开进程可执行文件的确切文件名。

  • IsSubsystemProcess
    指示进程子系统类型的布尔值是Win32以外的子系统。

  • Reserved
    保留供系统使用。

  • ParentProcessId
    新进程的父进程的进程ID。 请注意,父进程不一定与创建新进程的进程相同。 新进程可以继承父进程的某些属性,如句柄或共享内存。 (进程创建者的进程ID由CreatingThreadId-> UniqueProcess给出。)

  • CreatingThreadId
    创建新进程的进程和线程的进程ID和线程ID。 CreatingThreadId-> UniqueProcess包含进程ID,而CreatingThreadId-> UniqueThread包含线程ID。

  • FileObject
    指向进程可执行文件的文件对象的指针。如果IsSubsystemProcess为TRUE,则此值可能为NULL。

  • ImageFileName
    指向保存可执行文件的文件名的UNICODE_STRING字符串的指针。 如果FileOpenNameAvailable成员为TRUE,则该字符串指定用于打开可执行文件的确切文件名。 如果FileOpenNameAvailable为FALSE,则操作系统可能仅提供部分名称。如果IsSubsystemProcess为TRUE,则此值可能为NULL。

  • CommandLine
    指向UNICODE_STRING字符串的指针,该字符串保存用于执行该过程的命令。 如果命令不可用,CommandLine为NULL。如果IsSubsystemProcess为TRUE,则此值可能为NULL。

  • CreationStatus
    用于进程创建操作返回的NTSTATUS值。 驱动程序可以将此值更改为错误代码,以防止创建进程。

4.实现原理

破解 PsSetCreateProcessNotifyRoutineEx 函数的使用限制

第一种方法

在讲解怎么使用 PsSetCreateProcessNotifyRoutineEx 函数来注册回调之前,先来讲解下 Windows 对这个函数做的限制:驱动程序必须有数字签名才能使用此函数。经逆向研究,内核通过 MmVerifyCallbackFunction 验证此回调是否合法, 但此函数只是简单的验证了一下 DriverObject->DriverSection->Flags 的值是不是为 0x20:

nt!MmVerifyCallbackFunction+0x75: 
fffff800`01a66865 f6406820   test    byte ptr [rax+68h],20h 
fffff800`01a66869 0f45fd     cmovne  edi,ebp

所以破解方法非常简单,只要把 DriverObject->DriverSection->Flags 的值按位或 0x20 即可。其中,DriverSection 是指向 LDR_DATA_TABLE_ENTRY 结构的值,要注意该结构在 32 位和 64 位系统下的定义。

// 注意32位与64位的对齐大小
#ifndef _WIN64
    #pragma pack(1)                               
#endif
typedef struct _LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    union
    {
        LIST_ENTRY HashLinks;
        struct
        {
            PVOID SectionPointer;
            ULONG CheckSum;
        };
    };
    union
    {
        ULONG TimeDateStamp;
        PVOID LoadedImports;
    };
    PVOID EntryPointActivationContext;
    PVOID PatchInformation;
    LIST_ENTRY ForwarderLinks;
    LIST_ENTRY ServiceTagLinks;
    LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
#ifndef _WIN64
    #pragma pack()
#endif

第二种方法

使用此函数, 一定要设置 IMAGE_OPTIONAL_HEADER 中的 DllCharacterisitics 字段设置为:IMAGE_DLLCHARACTERISITICS_FORCE_INTEGRITY 属性,该属性是一个驱动强制签名属性。
使用 VS2013 开发环境设置方式是:

  • 右击项目,选择属性

  • 选中配置属性中的链接器,点击命令行

  • 在其它选项中输入: /INTEGRITYCHECK 表示设置; /INTEGRITYCHECK:NO 表示不设置

这样,设置之后,驱动程序必须要进行驱动签名才可正常运行!

 

创建回调并监控进程创建

我们根据上面的函数介绍,大概知道实现的流程了吧。对于设置回调函数,直接调用 PsSetCreateProcessNotifyRoutineEx 函数来设置就好。传入设置的回调函数名称以及删除标志参数设置为 FALSE,表示创建回调函数。这样,就可以成功设置进程监控的回调函数了。

那么,我们的回调函数也并不复杂,它的函数声明为:

void SetCreateProcessNotifyRoutineEx(
    _In_        HANDLE                 ParentId,
    _In_        HANDLE                 ProcessId,
    _Inout_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo
);

回调函数的名称可以任意,但是返回值类型以及函数参数类型必须是固定的,不能变更。回调函数的第一个参数 ParentId 表示父进程ID,第二个参数 ProcessId 表示进程ID,第三个参数 CreateInfo 为 NULL 时,表示进程退出;不为 NULL 时,表示进程创建。那么,创建进程的信息就存储在 PS_CREATE_NOTIFY_INFO 结构体中。

我们可以从 PS_CREATE_NOTIFY_INFO 中获取进程名称、路径、命令行、PID等进程信息。同时,可以通过设置成员 CreationStatus 的值来控制进程是否创建。当 CreationStatus 的值为 STATUS_SUCCESS 表示创建进程,否则,不创建进程。例如不创建进程的时候,CreationStatus 可以为 STATUS_UNSUCCESSFUL 错误码。

当我们要删除回调设置的时候,只需要调用 PsSetCreateProcessNotifyRoutineEx 函数,传入回调函数名称以及删除标志参数设置为 TRUE。这样,就可以成功删除设置的回调函数了。

 

5.编码实现

编程方式绕过签名检查

// 编程方式绕过签名检查
BOOLEAN BypassCheckSign(PDRIVER_OBJECT pDriverObject)
{
#ifdef _WIN64
    typedef struct _KLDR_DATA_TABLE_ENTRY
    {
        LIST_ENTRY listEntry;
        ULONG64 __Undefined1;
        ULONG64 __Undefined2;
        ULONG64 __Undefined3;
        ULONG64 NonPagedDebugInfo;
        ULONG64 DllBase;
        ULONG64 EntryPoint;
        ULONG SizeOfImage;
        UNICODE_STRING path;
        UNICODE_STRING name;
        ULONG   Flags;
        USHORT  LoadCount;
        USHORT  __Undefined5;
        ULONG64 __Undefined6;
        ULONG   CheckSum;
        ULONG   __padding1;
        ULONG   TimeDateStamp;
        ULONG   __padding2;
    } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#else
    typedef struct _KLDR_DATA_TABLE_ENTRY
    {
        LIST_ENTRY listEntry;
        ULONG unknown1;
        ULONG unknown2;
        ULONG unknown3;
        ULONG unknown4;
        ULONG unknown5;
        ULONG unknown6;
        ULONG unknown7;
        UNICODE_STRING path;
        UNICODE_STRING name;
        ULONG   Flags;
    } KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#endif
    PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
    pLdrData->Flags = pLdrData->Flags | 0x20;
    return TRUE;
}

 

设置回调

// 设置回调函数
NTSTATUS SetProcessNotifyRoutine()
{
    NTSTATUS status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)ProcessNotifyExRoutine, FALSE);
    if (!NT_SUCCESS(status))
    {
        ShowError("PsSetCreateProcessNotifyRoutineEx", status);
    }
    return status;
}

回调函数

// 回调函数
VOID ProcessNotifyExRoutine(PEPROCESS pEProcess, HANDLE hProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
    // CreateInfo 为 NULL 时,表示进程退出;不为 NULL 时,表示进程创建
    if (NULL == CreateInfo)
    {
        return;
    }
    // 获取进程名称
    PCHAR pszImageFileName = PsGetProcessImageFileName(pEProcess);
    // 显示创建进程信息
    DbgPrint("[%s][%d][%wZ]\n", pszImageFileName, hProcessId, CreateInfo->ImageFileName);
    // 禁止指定进程(520.exe)创建 
    if (0 == _stricmp(pszImageFileName, "520.exe"))
    {
        // 禁止创建
        CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;
        DbgPrint("[禁止创建]\n");
    }
}

 

删除回调

// 删除回调函数
NTSTATUS RemoveProcessNotifyRoutine()
{
    NTSTATUS status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)ProcessNotifyExRoutine, TRUE);
    if (!NT_SUCCESS(status))
    {
        ShowError("PsSetCreateProcessNotifyRoutineEx", status);
    }
    return status;
}

程序测试

在Win10 64位上

在Win7 64位上

 

总结

这个程序实现起来并不复杂,关键是对 PsSetCreateProcessNotifyRoutineEx 函数要理解透彻,理解清楚回调函数中,PS_CREATE_NOTIFY_INFO 结构体的所有成员含义。这样,我们就可以获取进程信息,以及控制进程的创建。

注意,破解 PsSetCreateProcessNotifyRoutineEx 函数的使用限制有两种方式,一种是通过编程来解决;一种是通过 VS 开发环境和数字签名来解决。

参考

参考自《Windows黑客编程技术详解》一书

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值