當前位置:
首頁 > 新聞 > 如何利用Rootkit實現文件刪除保護

如何利用Rootkit實現文件刪除保護

簡單來說,Rootkit就是一個數字工具箱,惡意軟體或木馬軟體,可以通過它來隱藏自身及指定的文件、進程和網路鏈接等信息。隱藏的過程是通過Rootkit載入到系統內核中,並通過修改內核達到隱蔽的目地,比如讓系統認為惡意軟體佔用的空間為壞塊,從而避免被檢測到。

Windows內核模式驅動程序和I/O請求數據包

由於這不是一篇關於內核模式或一般驅動程序的介紹性文章,所以我會對裡面提到的基本概念一帶而過。首先是所謂的「I/O請求數據包」(簡稱IRP),發送到設備驅動程序的大部分請求都打包在I/O請求數據包(IRP)中,然後操作系統組件或驅動程序將IRP發送到驅動程序,通常IRP由在堆棧中排列的多個驅動程序進行處理。堆棧中的每個驅動程序都與一個設備對象關聯。如果IRP由設備堆棧進行處理,則通常首先發送IRP至設備堆棧中的頂部設備對象。例如,如果IRP由此圖中顯示的設備堆棧進行處理,則會首先將IRP發送至設備堆棧頂部的篩選器設備對象(篩選器 DO)。

IRP可以是文件請求或鍵盤輸入內容等,IRP和驅動程序的作用是IRP被發送到已註冊(與I/O管理器)處理它們的堆棧中的驅動程序。

這樣,驅動程序沿著設備堆棧向下傳遞IRP,直到它到達能夠處理指定請求的設備或驅動程序,一旦指定請求處理完畢,又會沿著設備堆棧向上傳遞IRP。請注意,某些IRP沿著設備堆棧一路向下傳遞至物理設備對象(PDO)。其他IRP從未到達PDO,原因是這些IRP由PDO之上的驅動程序之一完成。

文件刪除保護

在本文中,我將介紹如何保護文件不被刪除的高級概念,為了防止文件被刪除,我選擇的條件是該文件必須具有. protected擴展(不區分大小寫)。我剛剛已經介紹了,驅動程序沿著設備堆棧向下傳遞IRP,直到它到達能夠處理指定請求的設備或驅動程序。如果在執行目標IRP之前,可以將特殊驅動程序插入驅動程序堆棧中的某個位置,那麼它就有能力過濾請求並在需要時中斷或修改它,這個概念就是文件刪除保護機制的核心思想。

為了檢測文件刪除中的IRP是否被中斷或修改,我只需要提取文件擴展名並將其與任何不允許刪除的內容進行比較。如果擴展名匹配,則驅動程序將通過完成請求並將錯誤發送回驅動程序堆棧來阻止IRP進行任何進一步處理。

具體保護過程

以下代碼是「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 }

};

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.
");

// Register the minifilter with the filter manager.

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

if (!NT_SUCCESS(status)) {

DBG_PRINT("Failed to register filter: .
", status);

return status;

}

// Start filtering I/O.

status = FltStartFiltering(Filter);

if (!NT_SUCCESS(status)) {

DBG_PRINT("Failed to start filter: .
", 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.
");

// 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

* 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。

注意,此文是我的一篇探索性文章,一些技術還不太成熟,如果你認為有不對的地方,可以反饋給我們。

參考及來源:

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


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

Facebook因劍橋分析數據泄露而受罰50萬英鎊
Gartner分享2018年Top10安全項目

TAG:嘶吼RoarTalk |