65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C++ 和 Windows 进行内核级应用程序黑名单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (5投票s)

2024 年 6 月 19 日

CPOL

6分钟阅读

viewsIcon

7225

downloadIcon

140

使用 C++ 开发和实现内核级应用程序黑名单的综合指南,以增强系统安全性。

应用程序黑名单简介

应用程序黑名单是一种安全措施,可确保只有已批准的应用程序才能在系统上执行。在 Windows 操作系统上实施此技术需要直接与内核交互,从而为防止未经授权的应用程序提供强大的保护层。

这种方法利用 Filter Manager 框架与内核进行交互,从而监控和控制应用程序的执行。

背景

应用程序黑名单是一种安全方法,它只允许预先批准的软件在系统上运行,从而有效阻止未经授权或可能有害的应用程序。当在内核级别实施此技术时,它尤其强大,因为它为可能绕过用户空间防御的恶意代码提供了强大的保护层。内核是操作系统最核心的部分,对系统操作和资源拥有最高级别的控制权。本文介绍了应用程序白名单的概念及其重要性,深入探讨了使用 C++ 进行内核编程的细节,并指导读者完成开发内核级白名单机制的过程。理解这些基础概念将使开发人员能够创建更安全、更有弹性的系统。

内核级应用程序黑名单工作流程图说明

此工作流程图说明了内核级应用程序黑名单的过程,重点关注系统内不同组件和决策点之间的交互。以下是对每个步骤和组件的详细说明:

组件和流程

用户应用程序:表示尝试在系统上执行的应用程序。

Filter Manager:Windows 内核的中间件,负责管理用于 I/O 操作预/后处理的各种过滤器(MiniFilters)。

MiniFilter 驱动程序:一种特殊的文件系统筛选器驱动程序,可以拦截文件 I/O 操作。它负责检查正在执行的可执行文件的属性。

DoesExecutableExistInPath:此函数检查尝试运行的可执行文件是否存在于预定义的黑名单路径中。如果可执行文件存在于路径中(是),则进入下一步。如果不存在(否),则将其添加到阻止列表中。

阻止列表:包含不允许执行的可执行文件列表,因为它们不在黑名单路径中。

PreCreate (IRP_MJ_CREATE) 预处理:一个决策点,用于确定文件名是否与现有定义的应用程序名称匹配。如果文件被列入黑名单,它将进入 Passthrough 步骤。如果未被列入黑名单,它将被阻止(未明确显示,但可通过箭头指向阻止列表推断)。

PassThrough:如果文件创建请求通过了之前的检查,此步骤将允许请求通过驱动程序,I/O 将由较低级别的驱动程序处理。

较低驱动程序:代表堆栈中较低级别的驱动程序,如果请求通过了所有先前的检查,它们将处理该请求。可执行文件将被允许继续执行。

使用代码

DriverEntry 函数初始化 MiniFilter 驱动程序,将其注册到 Filter Manager,并开始过滤 I/O 操作。可选地,它还可以注册一个进程创建通知回调。该函数处理成功和失败情况,确保在任何步骤失败时进行适当的清理。此设置对于 MiniFilter 开始运行和拦截系统上相关的 I/O 操作至关重要。

注意:我尝试通过两种方式来演示如何阻止应用程序:进程创建通知和 PreCreate() 方法。

extern "C"
NTSTATUS
DriverEntry(
    __in PDRIVER_OBJECT DriverObject,
    __in PUNICODE_STRING RegistryPath
)
{
    NTSTATUS status = STATUS_SUCCESS;

    //DbgBreakPoint();

    UNREFERENCED_PARAMETER(RegistryPath);

    KdPrint(("PocMiniFilter!DriverEntry: Entered\n"));

    MiniFilterData.DriverObject = DriverObject;

    //
    //  Register with FltMgr with our callback routines.
    //

    status = FltRegisterFilter( DriverObject,
                                &FilterRegistration,
                                &MiniFilterData.Filter);

    FLT_ASSERT(NT_SUCCESS(status));

    if (NT_SUCCESS(status)) {

        //
        //  Start filtering i/o
        //

        status = FltStartFiltering(MiniFilterData.Filter);

        if (!NT_SUCCESS(status)) {

            FltUnregisterFilter(MiniFilterData.Filter);
        }
    }

#ifdef _USE_NOTIFY_WAY
    status = PsSetCreateProcessNotifyRoutineEx(NotificationCallback, FALSE);
    if (!NT_SUCCESS(status)) {

        KdPrint(("PsSetCreateProcessNotifyRoutineEx failed with status = 0x%x\n", status));
    }
#endif
    status = STATUS_SUCCESS;

    return status;
}

FilterUnload 函数简介

PocMiniFilterUnload 函数处理 MiniFilter 驱动程序的卸载过程。它将过滤器从 Filter Manager 中注销,并在之前设置了进程创建通知回调的情况下,选择性地移除它。此函数确保驱动程序分配的所有资源都得到妥善释放,从而允许驱动程序干净安全地从系统中卸载。

NTSTATUS
BlacklistingAppUnload(
    __in FLT_FILTER_UNLOAD_FLAGS Flags
)

{
    UNREFERENCED_PARAMETER(Flags);

    PAGED_CODE();

    KdPrint(("BlacklistingApp!PtUnload: Entered\n"));

    FltUnregisterFilter(MiniFilterData.Filter);

#ifdef _USE_NOTIFY_WAY
    PsSetCreateProcessNotifyRoutineEx(NotificationCallback, TRUE);
#endif
    return STATUS_SUCCESS;
}

NotificationCallback 函数

NotificationCallback 函数是 MiniFilter 驱动程序的重要组成部分,用于监控进程创建事件。它检查正在执行的可执行文件是否在阻止列表中,并通过将创建状态设置为 STATUS_ACCESS_DENIED 来阻止其执行。这确保了未经授权或可能有害的可执行文件不允许在系统上运行。

VOID
NotificationCallback(
    _Inout_ PEPROCESS Process,
    _In_ HANDLE ProcessId,
    _In_opt_ PPS_CREATE_NOTIFY_INFO CreateInfo)
{
    UNREFERENCED_PARAMETER(Process);
    UNREFERENCED_PARAMETER(ProcessId);

    if (CreateInfo != NULL) {

        KdPrint(("NotificationCallback: IN = %wZ, CL =%wZ\n", CreateInfo->ImageFileName, CreateInfo->CommandLine));
       
        if (CreateInfo->ImageFileName) {
            if (DoesExecutableExistInPath(CreateInfo->ImageFileName)) 
            {
                CreateInfo->CreationStatus = STATUS_ACCESS_DENIED;
                KdPrint(("Execution blocked by Policy \n"));
            }
        }
    }
}

PreCreateCallback 简介

PreCreateCallback() 函数拦截文件创建请求,构建完整的文件路径,并检查文件是否在阻止列表中。如果文件被阻止,它将拒绝访问。该函数确保资源的正确清理,并处理各种验证和提前退出场景,以维护系统稳定性和安全性。

/*
    MiniFilter callback routines.

    Minifilter driver's PreCreate() callback routine, will check the conditions
    to monitor and potentially restrict certain types of file operations for 
    system security.

*/

FLT_PREOP_CALLBACK_STATUS
BlacklistingAppPreCallback(
    _Inout_ PFLT_CALLBACK_DATA Data,
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _Flt_CompletionContext_Outptr_ PVOID* CompletionContext
)
{
    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext = NULL);
    UNREFERENCED_PARAMETER(Data);

    PAGED_CODE();

    PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
    FLT_FILE_NAME_OPTIONS NameOptions = {0,};
    NTSTATUS status = STATUS_SUCCESS;
    PFILE_OBJECT FileObject = NULL;
    UNICODE_STRING FileName = { 0 };
    UNICODE_STRING fullPathName = { 0 };
    UNICODE_STRING Drive_Location = { 0, };
    FLT_FILESYSTEM_TYPE VolumeFilesystemType;
    BOOLEAN isNetPath = FALSE;
    FLT_PREOP_CALLBACK_STATUS callbackStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;

    __try
    {
        // We only registered for this irp, so thats all we better get!
        NT_ASSERT(Data->Iopb->MajorFunction == IRP_MJ_CREATE);

        // Check the Irql here if APC return with STATUS_UNSUCCESSFUL.
        if (KeGetCurrentIrql() > APC_LEVEL) {
            BlacklistingAppLeave(status);
        }

        FileObject = FltObjects->FileObject;
        if (FileObject == NULL)
        {
            // Can't get a name when there's no file object
            KdPrint(("No FileObject found in PreCreateCallback\n"));
            return callbackStatus;
        }

        if (FlagOn(Data->Iopb->OperationFlags, SL_OPEN_PAGING_FILE)) {
            return callbackStatus;
        }

        //  Opens by file ID are name agnostic. Thus we do not care about this open.
        if (FlagOn(Data->Iopb->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID)) {
            return callbackStatus;
        }

        status = FltGetFileSystemType(FltObjects->Instance, &VolumeFilesystemType);
        if (!NT_SUCCESS(status)){
            return FLT_PREOP_SUCCESS_NO_CALLBACK;
        }

        if (FLT_FSTYPE_MUP == VolumeFilesystemType)
        {
            KdPrint(("PreCreate : FLT_FSTYPE_MUP\n"));
            isNetPath = TRUE;
            NameOptions = FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_FILESYSTEM_ONLY;
        }
        else {
            isNetPath = FALSE;
            NameOptions = FLT_FILE_NAME_NORMALIZED;
        }

        // To get a file path using filter manager API.
        status = FltGetFileNameInformation(Data,
                                           NameOptions,
                                           &nameInfo);
        if (!NT_SUCCESS(status)) {

            KdPrint(("PreCreate: failed for file =%wZ, with status = 0x%x\n", Data->Iopb->TargetFileObject->FileName , status));
            BlacklistingAppLeave(status);
        }

        status = FltParseFileNameInformation(nameInfo);
        if (!NT_SUCCESS(status)) {
            KdPrint(("PreCreate: FltParseFileNameInformation failed, status: 0x%x \n", status));
            BlacklistingAppLeave(status);
        }

        if (nameInfo == NULL ||
            nameInfo->Name.Buffer == NULL ||
            nameInfo->Name.Length == 0)
        {
            KdPrint(("PreCreate: nameInfo is NULL: exit\n"));
            __leave;
        }

        if (NT_SUCCESS(status)) {
            if (nameInfo->Name.MaximumLength <= 32767){
                status = IoVolumeDeviceToDosName(FltObjects->FileObject->DeviceObject, &Drive_Location);

                if (NT_SUCCESS(status))
                {
                    fullPathName.Length = 0;
                    fullPathName.MaximumLength =
                        (USHORT)Drive_Location.MaximumLength + Data->Iopb->TargetFileObject->FileName.MaximumLength + 2;
                    fullPathName.Buffer = (PWCH) ExAllocatePoolWithTag(NonPagedPool, 
                                                                       fullPathName.MaximumLength, 
                                                                       'VedR');
                    if (fullPathName.Buffer == NULL) {
                        status = STATUS_INSUFFICIENT_RESOURCES;
                        BlacklistingAppLeave(status);
                    }

                    // The maximum length does not exceed the USHORT limit
                    if (fullPathName.MaximumLength > 0xFFFF) {
                        status = STATUS_BUFFER_OVERFLOW;
                        BlacklistingAppLeave(status);
                    }

                    RtlCopyUnicodeString(&fullPathName, &Drive_Location);
                    RtlAppendUnicodeStringToString(&fullPathName, &Data->Iopb->TargetFileObject->FileName);

                    KdPrint(("%wZ \r\n", fullPathName));
                }
            }
        }

        if (DoesExecutableExistInPath(&fullPathName)) {
            status = STATUS_ACCESS_DENIED;
            KdPrint(("Application Blocked by Policy \n"));
            return FLT_PREOP_COMPLETE;
        }
    }
    __finally
    {
        //  Release the name information structure (if defined)
        if (NULL != nameInfo) {

            FltReleaseFileNameInformation(nameInfo);
        }

        //  Release the name information structure (if defined)
        if (NULL != fullPathName.Buffer &&
            fullPathName.MaximumLength > 0) {

            ExFreePoolWithTag(fullPathName.Buffer,
                'VedR');
            fullPathName.Length = 0;
            fullPathName.MaximumLength = 0;
        }
    }

    return callbackStatus;
}

阻止算法

DoesExecutableExistInPath 函数检查给定的文件路径是否对应于应被阻止的可执行文件。它从完整路径中提取文件名,并将其与预定义的阻止可执行文件列表进行比较。

BOOLEAN 
DoesExecutableExistInPath(PCUNICODE_STRING fullPath) 
{
    UNICODE_STRING fileName = { 0, };

    // Check if fullPath is NULL or empty
    if (fullPath == NULL || fullPath->Length == 0) {
        return FALSE;
    }

    if (!ExtractFileName(fullPath, &fileName)) {
        return FALSE;
    }

    KdPrint(("Extracted file name: %wZ\n", fileName));

    // Array of executables to block
    PCWSTR blockedExecutables[] = {
        L"Notepad.exe",
        L"Calc.exe",
        L"Calculator.exe",
        L"CalculatorApp.exe",
        L"Excel.exe"
    };

    // Iterate in array and check for executable(s).
    for (int i = 0; i < ARRAYSIZE(blockedExecutables); i++) 
    {
        UNICODE_STRING executableName;
        RtlInitUnicodeString(&executableName, blockedExecutables[i]);

        if (ContainsSubstringIgnoreCase(&fileName, &executableName)) {
            KdPrint(("Blocked executable found: %wZ\n", executableName));
            return TRUE;
        }
    }

    return FALSE;
}
BOOLEAN 
ContainsSubstringIgnoreCase(PUNICODE_STRING source, PCUNICODE_STRING substring) 
{
    if (source == NULL || substring == NULL || source->Buffer == NULL || substring->Buffer == NULL) {
        return FALSE;
    }

    if (source->Length < substring->Length) {
        return FALSE;
    }

    for (size_t i = 0; i <= static_cast<size_t>(source->Length) - static_cast<size_t>(substring->Length); i += sizeof(WCHAR)) 
    {
        BOOLEAN match = TRUE;
        for (size_t j = 0; j < substring->Length / sizeof(WCHAR); j++) {
            if (RtlUpcaseUnicodeChar(source->Buffer[i / sizeof(WCHAR) + j]) !=
                RtlUpcaseUnicodeChar(substring->Buffer[j])) {
                match = FALSE;
                break;
            }
        }
        if (match) {
            return TRUE;
        }
    }

    return FALSE;
}

BOOLEAN 
ExtractFileName(PCUNICODE_STRING fullPath, PUNICODE_STRING fileName)
{
    if (fullPath == NULL || fullPath->Length == 0 || fileName == NULL) {
        return FALSE;
    }

    WCHAR* buffer = fullPath->Buffer;
    ULONG length = fullPath->Length / sizeof(WCHAR);

    for (ULONG i = length - 1; i > 0; i--)
    {
        if (buffer[i] == L'\\' || buffer[i] == L'/') {
            fileName->Buffer = &buffer[i + 1];
            fileName->Length = (USHORT)((length - i - 1) * sizeof(WCHAR));
            fileName->MaximumLength = fileName->Length;
            return TRUE;
        }
    }

    // if not found, return directly.
    RtlCopyUnicodeString(fileName, fullPath);

    return TRUE;
}

安装

安装文件系统 MiniFilter 驱动程序的正确方法是使用 INF 文件。INF 文件用于安装基于硬件的设备驱动程序。但也可用于在 Windows 系统上安装任何驱动程序。对 INF 文件的完整介绍超出了本文的范围。

;;;
;;; BlacklistingApp inf file.
;;;

;/**************************************************************************
; *                                                                        *
; * Copyright (C) 2024 Rahul Dev Tripathi                                  *
; *                                                                        *
; * This file is part of the [Application Blacklisting].                   *
; *                                                                        *
; * Permission is hereby granted, free of charge, to any person obtaining  *
; * a copy of this software and associated documentation files (the        *
; * "Software"), to deal in the Software without restriction, including    *
; * without limitation the rights to use, copy, modify, merge, publish,    *
; * distribute, sublicense, and/or sell copies of the Software, and to     *
; * permit persons to whom the Software is furnished to do so, subject to  *
; * the following conditions:                                              *
; *                                                                        *
; * The above copyright notice and this permission notice shall be         *
; * included in all copies or substantial portions of the Software.        *
; *                                                                        *
; * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     *
; * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
; * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   *
; * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   *
; * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      *
; * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 *
; *                                                                        *
; * For commercial use or if used in production environments,              *
; * please contact Rahul Dev Tripathi at r.tripathi.cse@gmail.com for      *
; * permission.                                                            *
; *                                                                        *
; **************************************************************************/


[Version]
Signature   = "$Windows NT$"
Class       = "ActivityMonitor"                         ;This is determined by the work this filter driver does
ClassGuid   = {b86dff51-a31e-4bac-b3cf-e8cfe75c9fc2}    ;This value is determined by the Class
Provider    = %Msft%
DriverVer   = 12/05/2024,1.0.0.0
CatalogFile = BlacklistingApp.cat
;PnpLockdown=0


[DestinationDirs]
DefaultDestDir          = 12
MiniFilter.DriverFiles     = 12            ;%windir%\system32\drivers

;;
;; Default install sections
;;

[DefaultInstall]
OptionDesc          = %ServiceDescription%
CopyFiles           = MiniFilter.DriverFiles

[DefaultInstall.Services]
AddService          = %ServiceName%,,MiniFilter.Service

;;
;; Default uninstall sections
;;

[DefaultUninstall]
DelFiles   = MiniFilter.DriverFiles

[DefaultUninstall.Services]
DelService = %ServiceName%,0x200      ;Ensure service is stopped before deleting

;
; Services Section
;

[MiniFilter.Service]
DisplayName      = %ServiceName%
Description      = %ServiceDescription%
ServiceBinary    = %12%\%DriverName%.sys        ;%windir%\system32\drivers\
Dependencies     = FltMgr
ServiceType      = 2                            ;SERVICE_FILE_SYSTEM_DRIVER
StartType        = 3                            ;SERVICE_DEMAND_START
ErrorControl     = 1                            ;SERVICE_ERROR_NORMAL
LoadOrderGroup   = "FSFilter Activity Monitor"
AddReg           = MiniFilter.AddRegistry

;
; Registry Modifications
;

[MiniFilter.AddRegistry]
HKR,,"DebugFlags",0x00010001 ,0x0
HKR,,"SupportedFeatures",0x00010001,0x3
HKR,"Instances","DefaultInstance",0x00000000,%DefaultInstance%
HKR,"Instances\"%Instance1.Name%,"Altitude",0x00000000,%Instance1.Altitude%
HKR,"Instances\"%Instance1.Name%,"Flags",0x00010001,%Instance1.Flags%


;
; Copy Files
;

[MiniFilter.DriverFiles]
%DriverName%.sys

[SourceDisksFiles]
BlacklistingApp.sys = 1,,

[SourceDisksNames]
1 = %DiskId1%,,,

;;
;; String Section
;;

[Strings]
Msft                    = "BlacklistingApp  Driver"
ServiceDescription      = "BlacklistingApp  driver"
ServiceName             = "BlacklistingApp"
DriverName              = "BlacklistingApp"
UserAppName             = "BlacklistingApp"
DiskId1                 = "MiniFilter Device Installation Disk"

;Instances specific information.
DefaultInstance         = "BlacklistingApp Instance"
Instance1.Name          = "BlacklistingApp Instance"
Instance1.Altitude      = "370000"
Instance1.Flags         = 0x0          ; Suppress automatic attachments

;
; Filter manager definitions
;
FltMgrServicesKey    = "SYSTEM\CurrentControlSet\Services\FltMgr\"
AttachWhenLoaded     = "AttachWhenLoaded"



有关如何修改 INF 的更多信息,请参阅 https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/creating-an-inf-file-for-a-file-system-driver

安装驱动程序

一旦 INF 文件被充分修改并编译了驱动程序代码,就可以安装了。最简单的安装方法是将驱动程序包(SYS、INF 和 CAT 文件)复制到目标系统,然后右键单击文件资源管理器中的 INF 文件并选择“安装”。这将运行 INF。

注意:如果在 INF 中,驱动程序的启动类型为 0,则重启系统,驱动程序将在启动时加载。对于此示例,驱动程序的启动类型为按需启动,因此无需重启。

此时,POC MiniFilter 驱动程序已安装,并可以使用 fltmc 命令行工具(使用提升的命令提示符)加载并运行。

C:\> fltmc load BlacklistingApp

一旦驱动程序加载到系统中,InstanceSetup 回调将识别设备总线类型并采取相应措施。

要从系统中卸载驱动程序,请使用以下命令:

c:\> fltmc unload BlacklistingApp.sys

测试环境准备

您可以暂时禁用数字驱动程序签名检查来启动 Windows 驱动程序测试。在 Windows 11 中,您可以按以下方式执行此操作:

  1. bcdedit /set nointegritychecks on
  2. bcdedit /set debug on
  3. bcdedit /set testsigning on
  4. 重新启动计算机,然后:
  5. 按住 **Shift** 键,然后在 Windows 主菜单中选择 **重启** 选项。
  6. 选择 **疑难解答** -> **高级选项** -> **启动设置** -> **重启**
  7. 在 **启动设置** 中,按 **F7** 选择 **禁用驱动程序签名强制** 选项。

重要提示

本文提供了关于如何开发和实现内核级解决方案以增强系统安全性的教育性概述。虽然此处讨论的技术和示例旨在说明关键概念,但它们无意用作完全健壮、可用于生产的解决方案。适当的安全措施需要彻底的测试、验证以及对本指南范围之外的许多因素的考虑。

就这样 & RFC

就是这样!快来看看吧,并在文章中的任何内容上发布问题或评论。希望您觉得它很有价值。请随时指出其中的错误。我将在未来的版本中尝试纠正它们。

关注点

为应用程序编写内核级黑名单代码是一次非常有益的体验,它让我深入了解了 Windows 操作系统。它涉及处理文件路径规范化、内存管理和确保正确的 IRQL 级别等复杂任务。虽然充满挑战,但这个过程也很有趣且富有启发性,巧妙的实现和解决方案增加了开发如此强大的安全机制的整体满意度。

历史

  • 2024 年 6 月 19 日:首次发布
© . All rights reserved.