如何利用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
※Facebook因劍橋分析數據泄露而受罰50萬英鎊
※Gartner分享2018年Top10安全項目
TAG:嘶吼RoarTalk |