标题 简介 类型 公开时间
关联规则 关联知识 关联工具 关联文档 关联抓包
参考1(官网)
参考2
参考3
详情
[SAFE-ID: JIWO-2024-1691]   作者: 特仑苏 发表于: [2018-07-25]

本文共 [653] 位读者顶过

导语:简单来说,Rootkit就是一个数字工具箱,恶意软件或木马软件,可以通过它来隐藏自身及指定的文件、进程和网络链接等信息。

pastedGraphic.png

简单来说,Rootkit就是一个数字工具箱,恶意软件或木马软件,可以通过它来隐藏自身及指定的文件、进程和网络链接等信息。隐藏的过程是通过Rootkit加载到系统内核中,并通过修改内核达到隐蔽的目地,比如让系统认为恶意软件占用的空间为坏块,从而避免被检测到。

Windows内核模式驱动程序和I/O请求数据包


由于这不是一篇关于内核模式或一般驱动程序的介绍性文章,所以我会对里面提到的基本概念一带而过。首先是所谓的“I/O请求数据包”(简称IRP),发送到设备驱动程序的大部分请求都打包在I/O请求数据包(IRP)中,然后操作系统组件或驱动程序将IRP发送到驱动程序,通常IRP由在堆栈中排列的多个驱动程序进行处理。堆栈中的每个驱动程序都与一个设备对象关联。如果IRP由设备堆栈进行处理,则通常首先发送IRP至设备堆栈中的顶部设备对象。例如,如果IRP由此图中显示的设备堆栈进行处理,则会首先将IRP发送至设备堆栈顶部的筛选器设备对象(筛选器 DO)。

pastedGraphic_1.png

IRP可以是文件请求或键盘输入内容等,IRP和驱动程序的作用是IRP被发送到已注册(与I/O管理器)处理它们的堆栈中的驱动程序。

pastedGraphic_2.png

这样,驱动程序沿着设备堆栈向下传递IRP,直到它到达能够处理指定请求的设备或驱动程序,一旦指定请求处理完毕,又会沿着设备堆栈向上传递IRP。请注意,某些IRP沿着设备堆栈一路向下传递至物理设备对象(PDO)。其他IRP从未到达PDO,原因是这些IRP由PDO之上的驱动程序之一完成。

文件删除保护


在本文中,我将介绍如何保护文件不被删除的高级概念,为了防止文件被删除,我选择的条件是该文件必须具有. protected扩展(不区分大小写)。我刚刚已经介绍了,驱动程序沿着设备堆栈向下传递IRP,直到它到达能够处理指定请求的设备或驱动程序。如果在执行目标IRP之前,可以将特殊驱动程序插入驱动程序堆栈中的某个位置,那么它就有能力过滤请求并在需要时中断或修改它,这个概念就是文件删除保护机制的核心思想。

为了检测文件删除中的IRP是否被中断或修改,我只需要提取文件扩展名并将其与任何不允许删除的内容进行比较。如果扩展名匹配,则驱动程序将通过完成请求并将错误发送回驱动程序堆栈来阻止IRP进行任何进一步处理。

pastedGraphic_3.png

具体保护过程


以下代码是“minifilter”驱动程序的代码样本,该段代码负责处理文件系统请求。

// The callbacks array defines what IRPs we want to process.

CONST FLT_OPERATION_REGISTRATION Callbacks[] = {

{ IRP_MJ_CREATE, 0, PreAntiDelete, NULL }, // DELETE_ON_CLOSE creation flag.

{ IRP_MJ_SET_INFORMATION, 0, PreAntiDelete, NULL }, // FileInformationClass == FileDispositionInformation(Ex).

{ IRP_MJ_OPERATION_END }

};

[出自:jiwo.org]

CONST FLT_REGISTRATION FilterRegistration = {

sizeof(FLT_REGISTRATION), // Size

FLT_REGISTRATION_VERSION, // Version

0, // Flags

NULL, // ContextRegistration

Callbacks, // OperationRegistration

Unload, // FilterUnloadCallback

NULL, // InstanceSetupCallback

NULL, // InstanceQueryTeardownCallback

NULL, // InstanceTeardownStartCallback

NULL, // InstanceTeardownCompleteCallback

NULL, // GenerateFileNameCallback

NULL, // NormalizeNameComponentCallback

NULL // NormalizeContextCleanupCallback

};


PFLT_FILTER Filter;

static UNICODE_STRING ProtectedExtention = RTL_CONSTANT_STRING(L"PROTECTED");


NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {

// We can use this to load some configuration settings.

UNREFERENCED_PARAMETER(RegistryPath);


DBG_PRINT("DriverEntry called.\n");


// Register the minifilter with the filter manager.

NTSTATUS status = FltRegisterFilter(DriverObject, &FilterRegistration, &Filter);

if (!NT_SUCCESS(status)) {

DBG_PRINT("Failed to register filter: <0x%08x>.\n", status);

return status;

}


// Start filtering I/O.

status = FltStartFiltering(Filter);

if (!NT_SUCCESS(status)) {

DBG_PRINT("Failed to start filter: <0x%08x>.\n", status);

// If we fail, we need to unregister the minifilter.

FltUnregisterFilter(Filter);

}


return status;

}

首先,应由驱动程序处理的IRP是IRP_MJ_CREATE 1和IRP_MJ_SET_INFORMATION 1,它们分别是在创建文件(或目录)和设置元数据时发出的请求。这两个IRP都能够删除文件,至于具体原因我在稍后会详细介绍。 Callbacks数组定义了要处理的相应IRP以及预操作和操作后回调函数。预操作定义了当IRP进入堆栈时所调用的函数,而后操作是在IRP完成后重新启动时调用的函数。请注意,由于此操作中后操作为NULL,因此拦截文件删除的操作只在预操作中进行处理。

DriverEntry是驱动程序的主要函数,通常会用这个函数来填充dispatch例程的指针,这就象注册回调函数一样。有的设备要创建设备的对象,或者还要创建一个设备名字,以及其他的初始化操作。它的原型如下:

NTSTATUS DriverEntry(

    IN PDRIVER_OBJECT DriverObject,

    IN PUNICODE_STRING RegistryPath 

    ){

}

使用FltRegisterFilter执行过滤器管理器的注册,一旦注册成功,就要开始过滤IRP,它必须使用过滤器句柄调用FltStartFiltering函数。还要请注意,如前所述,我已将扩展名定义为.PROTECTED。

定义卸载函数也是一种很好的做法,这样,如果驱动程序被请求停止,则定义的卸载函数就可以执行必要的清理。定义卸载函数在目前的设计中只是个补充,并不是主要方向。

/*

 * This is the driver unload routine used by the filter manager.

 * When the driver is requested to unload, it will call this function

 * and perform the necessary cleanups.

 */

NTSTATUS Unload(_In_ FLT_FILTER_UNLOAD_FLAGS Flags) {

UNREFERENCED_PARAMETER(Flags);

DBG_PRINT("Unload called.\n");

// Unregister the minifilter.

FltUnregisterFilter(Filter);

return STATUS_SUCCESS;

}

此段代码中的最后一个函数是PreAntiDelete预操作回调,它负责处理IRP_MJ_CREATE和IRP_MJ_SET_INFORMATION IRP。 IRP_MJ_CREATE包括请求打开“文件句柄或文件对象或设备对象”的函数,例如ZwCreateFile。 IRP_MJ_SET_INFORMATION包括请求设置“关于文件或文件句柄的元数据”的函数,例如ZwSetInformationFile。

/*

 * This routine is called every time I/O is requested for:

 * - file creates (IRP_MJ_CREATE) such as ZwCreateFile and 

 * - file metadata sets on files or file handles 

 *   (IRP_MJ_SET_INFORMATION) such as ZwSetInformation.

 *

 * This is a pre-operation callback routine which means that the

 * IRP passes through this function on the way down the driver stack

 * to the respective device or driver to be handled.

 */

FLT_PREOP_CALLBACK_STATUS PreAntiDelete(_Inout_ PFLT_CALLBACK_DATA Data, _In_ PCFLT_RELATED_OBJECTS FltObjects, _Flt_CompletionContext_Outptr_ PVOID *CompletionContext) {

UNREFERENCED_PARAMETER(CompletionContext);

/* 

 * This pre-operation callback code should be running at 

 * IRQL <= APC_LEVEL as stated in the docs:

 * https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/writing-preoperation-callback-routines

 * and both ZwCreateFile and ZwSetInformaitonFile are also run at 

 * IRQL == PASSIVE_LEVEL:

 * - ZwCreateFile: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntcreatefile#requirements

 * - ZwSetInformationFile: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntsetinformationfile#requirements

 */

PAGED_CODE();

/*

 * By default, we don't want to call the post-operation routine

 * because there's no need to further process it and also

 * because there is none.

 */

FLT_PREOP_CALLBACK_STATUS ret = FLT_PREOP_SUCCESS_NO_CALLBACK;

// We don't care about directories.

BOOLEAN IsDirectory;

NTSTATUS status = FltIsDirectory(FltObjects->FileObject, FltObjects->Instance, &IsDirectory);

if (NT_SUCCESS(status)) {

if (IsDirectory == TRUE) {

return ret;

}

}

/*

 * We don't want anything that doesn't have the DELETE_ON_CLOSE 

 * flag.

 */

if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) {

if (!FlagOn(Data->Iopb->Parameters.Create.Options, FILE_DELETE_ON_CLOSE)) {

return ret;

}

}

/*

 * We don't want anything that doesn't have either 

 * FileDispositionInformation or FileDispositionInformationEx or 

 * file renames (which can just simply rename the extension).

 */

if (Data->Iopb->MajorFunction == IRP_MJ_SET_INFORMATION) {

switch (Data->Iopb->Parameters.SetFileInformation.FileInformationClass) {

case FileRenameInformation:

case FileRenameInformationEx:

case FileDispositionInformation:

case FileDispositionInformationEx:

case FileRenameInformationBypassAccessCheck:

case FileRenameInformationExBypassAccessCheck:

case FileShortNameInformation:

break;

default:

return ret;

}

}

/*

 * Here we can check if we want to allow a specific process to fall 

 * through the checks, e.g. our own application.

 * Since this is a PASSIVE_LEVEL operation, we can assume(?) that 

 * the thread context is the thread that requested the I/O. We can  

 * check the current thread and compare the EPROCESS of the 

 * authenticated application like so:

 *

 * if (IoThreadToProcess(Data->Thread) == UserProcess) {

 *     return FLT_PREOP_SUCCESS_NO_CALLBACK;

 * }

 *

 * Of course, we would need to find and save the EPROCESS of the 

 * application somewhere first. Something like a communication port 

 * could work.

 */

PFLT_FILE_NAME_INFORMATION FileNameInfo = NULL;

// Make sure the file object exists.

if (FltObjects->FileObject != NULL) {

// Get the file name information with the normalized name.

status = FltGetFileNameInformation(Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT, &FileNameInfo);

if (NT_SUCCESS(status)) {

// Now we want to parse the file name information to get the extension.

FltParseFileNameInformation(FileNameInfo);

// Compare the file extension (case-insensitive) and check if it is protected.

if (RtlCompareUnicodeString(&FileNameInfo->Extension, &ProtectedExtention, TRUE) == 0) {

DBG_PRINT("Protecting file deletion/rename!");

// Strings match, deny access!

Data->IoStatus.Status = STATUS_ACCESS_DENIED;

Data->IoStatus.Information = 0;

// Complete the I/O request and send it back up.

ret = FLT_PREOP_COMPLETE;

}

// Clean up file name information.

FltReleaseFileNameInformation(FileNameInfo);

}

}

return ret;

}

对于IRP_MJ_CREATE,我会检查FILE_DELETE_ON_CLOSE创建选项,该选项的作用为“当文件的最后一个句柄传递给NtClose时,文件就会被删除”。如果设置了此选项,则必须在DesiredAccess参数中设置DELETE标志。如果FILE_DELETE_ON_CLOSE创建选项不存在,我们就不用关心这一步了。此时,因此IRP_MJ_CREATE将被传递到堆栈中,以FLT_PREOP_SUCCESS_NO_CALLBACK返回值代表进一步的处理结果。请注意,NO_CALLBACK意味着当IRP完成并返回堆栈时不应该调用后操作例程,因为没有后操作,所以这个函数应该返回堆栈。

对于IRP_MJ_SET_INFORMATION,应检查FileInformationClass参数。 FileDispositionInformation的作用是“通常,将FILE_DISPOSITION_INFORMATION的DeleteFile选项设置为TRUE,以便在调用NtClose时删除文件,以释放文件对象的最后一个打开句柄,调用者必须打开在DesiredAccess参数中设置了DELETE标志的文件”。为了防止文件被简单的重命名,从而使受保护的扩展不再存在,还必须检查FileRenameInformation和FileShortNameInformation值。

如果驱动程序收到选择进行文件删除的IRP请求,则必须使用FltGetFileNameInformation和FltParseFileNameInformation函数解析文件名信息以提取扩展名。然后,在删除扩展请求的文件和受保护扩展之间进行简单的字符串比较,以确定是否应该允许删除操作。如果文件未被授权删除,那在此种情况下,操作的状态就会被驱动程序设置为STATUS_ACCESS_DENIED,并且显示预操作函数已经完成IRP。

pastedGraphic_4.png

pastedGraphic_5.png

注意,此文是我的一篇探索性文章,一些技术还不太成熟,如果你认为有不对的地方,可以反馈给我们。

参考及来源:

https://0x00sec.org/t/kernel-mode-rootkits-file-deletion-protection/7616

https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/irp-mj-create

https://msdn.microsoft.com/library/windows/hardware/ff566424

如若转载,请注明原文地址: http://www.4hou.com/technology/12714.html

评论

暂无
发表评论
 返回顶部 
热度(653)
 关注微信