Windows驱动编程之文件过滤
一半人生 发表于 浙江 二进制安全 3711浏览 · 2024-02-02 09:19

引言

  Windows文件过滤驱动应用广泛,覆盖安全EDR/DLP,游戏反作弊,云存储,云游戏等商用场景,有良好的市场表现,本文主题过程性实践和文件过滤安全技术分享。

MiniFilter简介:

  MiniFilter(Mini-filter Installable File System)框架,MiniFilter拥有良好兼容性,高封装接口,加快软件开发周期和降低了难度门槛,也是微软官方推荐文件过滤框架。早期微软各版本驱动兼容性问题,开发周期变动大。对外统一接口,按照API来约束办事,也是一贯风格,解决安全挂钩合理性,减少开发者文件过滤驱动对系统带来的不确定因素。

用户请求IO(用户态出发),经历如下步骤:

1. 将I/O请求转发到文件系统.
2. 过滤管理拦截请求按照循序A-B-C来调用已注册的微型过滤器,然后将 I/O 请求转发到下一个较低的驱动程序。
3. 文件系统驱动程序处理并转发修改后的请求。
4. 目标卷的存储驱动程序堆栈准备对硬件请求(HAL)。

See Minifilter:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/ifs/advantages-of-the-filter-manager-model

Minifilter框架分析:

Miniflter Register Filter Callback

传统型文件过滤寻找对象生成过滤驱动绑定设备栈,编写分发,体积庞大,而新框架代码层面主要注册-启动-预操作-后操作四部分。IRP工作都交给了Filter Manager,非常清爽:

CONST FLT_REGISTRATION FilterRegistration = {

    sizeof(FLT_REGISTRATION),           //  Size
    FLT_REGISTRATION_VERSION,           //  Version
    0,                                  //  Flags

    NULL,                               //  Context
    Callbacks,                          //  Operation callbacks

    NULL,                               //  MiniFilterUnload

    FsFilter1InstanceSetup,                    //  InstanceSetup
    FsFilter1InstanceQueryTeardown,            //  InstanceQueryTeardown
    FsFilter1InstanceTeardownStart,            //  InstanceTeardownStart
    FsFilter1InstanceTeardownComplete,         //  InstanceTeardownComplete

    NULL,                               //  GenerateFileName
    NULL,                               //  GenerateDestinationFileName
    NULL                                //  NormalizeNameComponent
};
NTSTATUS status;
status = FltRegisterFilter(
           DriverObject,                  //Driver
           &FilterRegistration,           //Registration
           &MiniSpyData.FilterHandle);    //RetFilter
See Msdn: https://docs.microsoft.com/zh-cn/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-_flt_registration

CONST FLT_OPERATION_REGISTRATION结构体中第五个域,需要重点关注的

typedef struct _FLT_OPERATION_REGISTRATION {
  UCHAR                            MajorFunction;
  FLT_OPERATION_REGISTRATION_FLAGS Flags;
  PFLT_PRE_OPERATION_CALLBACK      PreOperation;
  PFLT_POST_OPERATION_CALLBACK     PostOperation;
  PVOID                            Reserved1;
} FLT_OPERATION_REGISTRATION, *PFLT_OPERATION_REGISTRATION;

参数一功能号,想拦截读操作,需要从IRP_MJ_READ入手,文件创建则需要IRP_MJ_CREATE入手。

IRP_MJ_CLEANUP
IRP_MJ_CLOSE
IRP_MJ_CREATE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_FILE_SYSTEM_CONTROL
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_INTERNAL_DEVICE_CONTROL
IRP_MJ_PNP
IRP_MJ_POWER
IRP_MJ_QUERY_INFORMATION
IRP_MJ_READ
IRP_MJ_SET_INFORMATION
IRP_MJ_SHUTDOWN
IRP_MJ_SYSTEM_CONTROL
IRP_MJ_WRITE

参数二

SKIP_CACHED_IO不过滤缓冲区读写。
SKIP_PAGING_IO不过滤分页读写。
SKIP_NON_DASD_IO仅对读写回调有用。

  参数三和四是功能函数,预操作和后操作回调,IRP攻防业务处理的地方。传统的过滤中,基于IRP分发和事件回调两种方式可以实现主要的功能模块,处理预操作回调和后操作回调即可,通常是在预操作回调实现过滤。

  预操作回调函数参数,可以从Data和Object对象中,获取I/O请求的基础数据,使用Flt系列API来获取文件信息和上下文。后操作回调函数在IRP完成后被调用,已完成动作基本不做太多处理。

Minifilter IPC

R0~R3 Minifilter提供了一套交互IPC,类似于创建Socket通信。

r0
// FltBuildDefaultSecurityDescriptor绑定安全描述符,FLT_PORT_ALL_ACCESS。
FltBuildDefaultSecurityDescriptor(&sd, FLT_PORT_ALL_ACCESS);

//  初始化属性对象OBJECT_ATTRIBUTES对象, 初始化通信端口定义名称MINSPY_PORT_NAME。
RtlSetDaclSecurityDescriptor(sd, TRUE, NULL, FALSE);
RtlInitUnicodeString(&EventPortName, L"\\HadesEventFltPort");
InitializeObjectAttributes(
    &oa,
    &EventPortName,
    OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
    NULL,
    sd
);
status = FltCreateCommunicationPort(
    g_FltServerPortEvnet,
    &g_FltServerPortEvnetPort,
    &oa,
    NULL,
    CommunicateConnect,
    CommunicateDisconnect,
    NULL,
    1
);

FltCreateCommunicationPort注册,这个地方有三个回调函数是必须处理的,利用FilterConnectCommunicationPort和FilterSendMessage进行交互。

r3
#include <fltuser.h>
// 连接驱动创建的Port
Status = FilterConnectCommunicationPort(
    L"\\HadesEventFltPort", // Driver CreatePortName
    0,
    NULL,
    0,
    NULL,
    &g_hPort);
if (Status == HRESULT_FROM_WIN32(S_OK))

// 绑定IOCP端口(异步接收)
g_comPletion = CreateIoCompletionPort(g_hPort, NULL, 0, 4);
if (nullptr == g_comPletion)
{
    CloseHandle(g_hPort);
    g_hPort = nullptr;
    continue;
}

// 获取消息分发处理
Status = FilterGetMessage(
    g_hPort,
    &msg->MessageHeader,
    FIELD_OFFSET(COMMAND_MESSAGE, Overlapped),
    &msg->Overlapped
);

Minifilter安全技术:

文件/目录保护:

  访问文件或目录,Windows下要通过文件句柄Handle进行操作,也就是Open或者Create。基于Minfilter推荐在IRP_MJ_CREATE PreCallback进行过滤操作,IRP_MJ_CREATE不一定全面,还会有删除/重命名/修改等属性,需要注册IRP_MJ_SET_INFORMATION。

{ IRP_MJ_CREATE,
0,
FsFilter1PreOperation,
NULL/*FsFilter1PostOperation*/},

{ IRP_MJ_SET_INFORMATION,
0,
FsFilter1PreOperation,
NULL },

  不同的IRP操作,注册回调可以使用同函数进行处理,可以都使用FsFilter1PreOperation前操作回调,通过IRP类别区分即可。r3通过CreateFile或者OpenFile等API访问文件,I/O到过滤文件系统以后,会触发我们注册的回调,需要业务规则检测访问权限。

  • IRP类别
const unsigned char IRP_MJ_CODE = Data->Iopb->MajorFunction;
if (IRP_MJ_CODE == IRP_MJ_CREATE) {

}
else if (IRP_MJ_CODE == IRP_MJ_SET_INFORMATION) {

}
  • 访问进程PID

    const DWORD dwPID = (DWORD)PsGetCurrentProcessId();
  • 通过Data参数获取被访问的文件或目录完整路径

    PFLT_FILE_NAME_INFORMATION pNameInfo = NULL;
    NTSTATUS status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &pNameInfo);
  • 访问目标属性,比如我们需要对IRP_MJ_SET_INFORMATION做细分识别

    Data->Iopb->Parameters.SetFileInformation.FileInformationClass;
    // Rename
    case FileRenameInformation:
    // IRP_MJ_SET_INFORMATION Rename触发NameInfo
    case FileNameInformation:
    // Delete
    case FileDispositionInformation:
  • 正常访问返回FLT_PREOP_SUCCESS_NO_CALLBACK,不允许访问FLT_PREOP_COMPLETE

    Data->IoStatus.Status = STATUS_ACCESS_DENIED;
    Data->IoStatus.Information = 0;
    return FLT_PREOP_COMPLETE;
  • IRP_MJ_CREATE权限

if (((Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff) == FILE_CREATE ||
    ((Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff) == FILE_OPEN_IF ||
    ((Data->Iopb->Parameters.Create.Options >> 24) & 0x000000ff) == FILE_OVERWRITE_IF)
- FILE_DELETE_ON_CLOSE也比较特殊

上述的过程性代码可以根据PID,被访问路径,访问的属性和权限进行业务规则,拒绝被恶意进程访问,可以使用FLT_PREOP_COMPLETE进行保护或者拦截。

文件隐藏:

  隐藏挂钩方式有很多种,应用层可以通过WINAPI来查询,递归枚举目录文件。大部分查找都是通过查询方式来做的,但基于Ntfs MFT磁盘解析,动作不会进入到文件过滤驱动IRP。
  Minifilter通过IRP查询拦截可以使用IRP_MJ_DIRECTORY_CONTROL过滤来实现,前回调FsFilterAntsDrPostFileHide处理,IRP处理不好会影响性能,访问枚举性能很高,基线条件要处理好。

{ IRP_MJ_DIRECTORY_CONTROL,
0,
FsFilterAntsDrPostFileHide,
NULL },
  • 请求属性获取IRP_MN_QUERY_DIRECTORY,判断FileBothDirectoryInformation.

    if (Data->Iopb->MinorFunction == IRP_MN_QUERY_DIRECTORY &&
      (Data->Iopb->Parameters.DirectoryControl.QueryDirectory.FileInformationClass == FileBothDirectoryInformation) &&
      Data->Iopb->Parameters.DirectoryControl.QueryDirectory.Length > 0 &&
      NT_SUCCESS(Data->IoStatus.Status))
      {
      }
  • IRP_MJ_DIRECTORY_CONTROL不能通过返回值来进行权限拒绝,需要通过映射MDL进行修改.

if (Data->Iopb->Parameters.DirectoryControl.QueryDirectory.MdlAddress != NULL)
{

    Bufferptr = MmGetSystemAddressForMdl(Data->Iopb->Parameters.DirectoryControl.QueryDirectory.MdlAddress,
        NormalPagePriority);
}
else
{
    Bufferptr = Data->Iopb->Parameters.DirectoryControl.QueryDirectory.DirectoryBuffer;
}

if (Bufferptr == NULL)
    return FLT_POSTOP_FINISHED_PROCESSING;

// 推荐使用 MmGetSystemAddressForMdlSafe
  • 获取映射的MDL,FILE_BOTH_DIR_INFORMATION进行摘除.
PFILE_BOTH_DIR_INFORMATION pCutFileInfo = (PFILE_BOTH_DIR_INFORMATION)Bufferptr;
PFILE_BOTH_DIR_INFORMATION pPreFileInfo = pCutFileInfo;
PFILE_BOTH_DIR_INFORMATION pNextFileInfo = 0;
ULONG uNextOffset = 0;
if (pCutFileInfo == NULL)
    return FLT_POSTOP_FINISHED_PROCESSING;

// 可以找到隐藏的文件名
pCutFileInfo->FileName, HideFileName, wcslen(HideFileName)

// 摘除
if (uNextOffset == 0)
    pPreFileInfo->NextEntryOffset = 0;
else
    pPreFileInfo->NextEntryOffset = (ULONG)((PCHAR)pCutFileInfo - (PCHAR)pPreFileInfo + uNextOffset);
pCutFileInfo = pNextFileInfo;

进程/模块拦截:

进程拦截,保护推荐使用PsSetCreateProcessNotifyRoutineEx、ObRegisterCallbacks.

// See: Available starting with Windows Vista with SP1 and Windows Server 2008.
// Msdn: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex
PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)Process_NotifyProcessEx, FALSE);

// See: Available starting with Windows Vista with Service Pack 1 (SP1) and Windows Server 2008.
// Msdn: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks
NTSTATUS status = ObRegisterCallbacks(&obReg, &g_handleobj);

// 卸载
PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)Process_NotifyProcessEx, TRUE);
ObUnRegisterCallbacks(g_handleobj);

  DLL拦截模块有多种方式,已落地文件在Open/Craete IRP做拦截,使其没有权限打开失败,基于Minfilter注册IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION也可以做到相同效果的拦截。

{ IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION,
0,
FsFilterAntsDrvPreExe,
NULL },
  • 加载的名字或PID
    const DWORD dwPID = (DWORD)PsGetCurrentProcessId();
    FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &pNameInfo)
  • PageProtection执行状态
    if (Data->Iopb->Parameters.AcquireForSectionSynchronization.PageProtection == PAGE_EXECUTE)
    {
      return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }
  • 拦截方式
    Data->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES // DLL弹出资源错误窗口,可以设置.
    Data->IoStatus.Status = STATUS_ACCESS_DENIED
    Data->Iostatus.information = 0;
    return FLT_PREOP_COMPLETE;
    实践中发现很多其他的调用也会进入到注册回调,这时候可以做PE区分,通过解析来区分有效性EXE/DLL模块,FltReadFile读取FileObject对象,拿到数据做NT解析即可。
0 条评论
某人
表情
可输入 255