《Windows內核安全與驅動編程》-第十一章文件系統的過濾與監控-day6

11.6 讀寫操作的過濾

11.6.1 設置一個讀處理函數

​ 文件系統有多種操作,但是讀寫操作仍然是最重要的。比如殺毒軟件對文件內容的讀寫進行監控,可以達到掃描病毒的目的。簡單一點就是,文件在被讀取時,殺毒軟件掃描其中是否含有病毒特徵碼,就可以防止一些病毒從硬盤被加載到內存中執行;如果任意文件在被寫入到磁盤時,也可以檢測其中寫入的內容是否含有病毒特徵碼,這樣就可以防止病毒被寫入硬盤,也就可以防止硬盤上的文件被感染。

​ 在之前的代碼中已經綁定了文件系統卷,而所有的文件都是保存在捲上的,所以處理截獲到的主功能號爲 IRP_MJ_READ/WRITE 的IRP,就可以實現掃描病毒特徵碼的功能。其中的關鍵就是可以從IRP中解析到要讀取和寫入到文件見的內容。

​ 回憶在之前入口函數中分發函數的設置,其中處理讀寫的分別是 SfRead 和 SfWrite。這兩個函數的處理過程非常詳細,但是要注意的是——如果要獲得操作的文件內容,讀請求必須是完成後才能看到內容,而寫請求可以直接得到。因爲寫請求是上層數據發送下來的,在請求完成之前就存在於緩衝區內;而讀請求必須從硬盤上獲得,因此讀請求的過濾更加複雜一些。下面的例子都是講述讀請求的處理的,而寫請求的處理不在追敘。

DriverObject->MajorFunction[IRP_MJ_READ] = SfRead;

11.6.2 設備對象的區分處理

​ 對於SfRead中的處理,首先需要判斷設備對象是不是一個綁定在文件系統卷設備上的過濾設備,如果是,那麼就是一個讀寫文件的操作。但是同時,綁定在文件系統的控制設備上的過濾設備也有可能發生讀請求,但是那就不是在讀取文件了。

​ 那麼如何判斷呢?記得綁定Volume的代碼已經在設備擴展中設置了域StorageDev,如果不是,那麼判斷StorageDev是否問空,那麼就知道這是否是一個文件系統的卷設備。由此可見,過濾設備上的設備擴展是非常有用的。實際上就是用來在綁定時保存任意信息,將來在過濾時能夠得到這些信息的上下文。

PSFILTER_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
if(devExt->StorageDev != NULL)
{
    ...
}

​ 其他的情況不需要捕獲,直接傳遞到下層。讀請求的IRP情況非常複雜,最好的學習方法就是打印IRP的各個細節,自己查看文件操作的完成過程。

​ 下面實現SfRead,這裏判斷處理稍有不同。如果是本程序自己的控制設備的讀操作,則返回失敗,因爲本程序的控制設備並沒有提供讀接口。然後判斷是不是文件讀操作,如果不是直接放過。

NTSTATUS SfRead(
		IN PDEVICE_OBJECT DeviceObject,
		IN PIRP Irp
)
{
    PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
    PFILE_OBJCET file_object = irpsp->FileObject;
    PSFILTER_DEVICE_EXTENSION devExt = DeviceObject->DeviceExtension;
    
    //對控制設備的讀操作直接返回失敗,不是讀操作直接跳過
    if(IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)){
        Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
        Irp->Iostatus.Information = 0;
        IoCompleteRequest(Irp,IO_NO_INCREMENT);
        return STATUS_INVALID_DEVICE_REQUEST;
    }
    //對其他設備的操作直接下發
    if(devExt->StorageDev != NULL)
    {
    	return SfPassThrough(DeviceObject,Irp);
    }
    //...
    
}

11.6.3 解析讀請求中的文件信息

​ 接下來的工作是分析這個IRP。當前從這個IRP中可以獲得一些文件信息,讀取的偏移量和讀取的長度。如

PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
LARGE_INTEGER offset;
ULONG length;

offset.QuadPart = irpsp->Parameters.Read.ByteOffset.QuadPart;
length = irpsp->Parameters.Read.Length;

​ 如果是殺毒軟件要掃描病毒,此時當然希望能得到所讀到的數據內容。數據只能在完成之後獲取,所以下面要設置完成函數。IRP的緩衝區放在那裏取決於這個操作的IO方式。IO方式分爲三種:緩衝方式、直接方式、和皆不是方式。對三種方式介紹如下:

  • 緩衝方式。這種方式在讀寫請求中沒有出現過。
  • 直接方式。這屆方式使用MDL來傳遞緩衝區的。因爲請求一般都是用戶進行發起的,原始的空間都在用戶空間中。這些空間指針如果傳遞到內核代碼裏,雖然內核可以正常使用,但是卻必須保證在當前進程範圍空間內。爲了避免麻煩,出現了直接方式,直接方式就是直接把用戶空間的範圍直接映射到內核空間。爲此種種操作,MDL都已經做好了。
  • 皆不是方式。該方式是最簡單的,直接把用戶空間的指針傳遞進來不做任何處理。這個指針就是Irp->UserBuffer。這個空間在SfRead中直接用當然是可以的,但是不可以被放到其他線程中去處理。一般可以設置一個事件,等待完成函數被調用後,在SfRead中處理。

​ 在實際處理中,優先使用MDL,其次使用UserBuffer。那麼獲取讀取的內容的主要方法示例如下:

KEVENT watiEvent;
void *buf;
ULONG length;
KeInitializeEvent(&waitEvent,NotiafactionEvent,FALSE);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,SfReadCompletion,&waitEvent,TRUE,TRUE,TRUE);
status = IoCallDriver(devExt->AttachedToDeviceObject,Irp);
if(STATUS_PEDING == status)
{
	status = KeWaitForSingleObject(&waitEvent,Executive,KernelMode,FALSE,NULL);
}
if(Irp->IoStatus.Status = STATUS_SUCCESS)
{
    //完成了,讀取到的內容在緩衝區BUFF中
    //長度在Irp-IoStatus.Information中
    if(Irp->MdlAddress != NULL)
    	buf = MmGetSystemAddressForMdl(Irp->MdlAddress);
    else
    	buf = Irp->UserBuffer;
    length = Irp->IoStatus.Information
}

11.6.4 讀請求的完成

​ 所謂的完成讀請求,是一個驅動程序報告它的上層驅動程序,一個IRP已經完成的過程。首先,任何驅動都能可以完成它收到的請求,對一個IRP而言,只要填好它需要的信息,然後就可以向上層報告這個IRP完成了。至於這些數據從哪裏來,上層並不關心。即使實際上這個驅動請求根本就沒有到達過下層,而是自己虛構完成的,也不會有什麼問題。

​ 一般的說,文件系統過濾驅動對IRP進行監控,允許或者禁止,或者對IRP進行修改,比如在寫之前,先把緩衝區中要寫的數據加密。但是還有一些情況需要自己完成這個請求。比如針對視頻文件的讀取,用戶希望可以任意拖動進度條來觀看整個視頻。當驅動針對特殊的視頻文件時,直接一次將文件讀取到內存中,而在下次收到讀取請求時候,就不必再訪問下層驅動而直接從內存中取數據。這時候就需要驅動自己完成請求了。

​ 這裏又要談到IRP的次功能號。其中 IRP_MJ_READ的次功能號有 IRP_MN_NORMAL、IRP_MN_MDL、IRP_MN_COMPLETE。還有其他集中情況,資料上有解釋,不在贅述。

  • IRP_MN_NORMAL的情況,既有可能在MDL中讀取數據也有可能在UserBuff裏讀取數據。人如果完成這樣的請求,就需要首先判斷一下MDL裏是否有數據。
  • IRP_MN_MDL。這種情況比較罕見,數據即不在MDL也不在UserBuffer。該請求是請編程者分配一個MDL,然後把MDL指向數據所在的空間最後返回給上層。這種一般結合 IRP_MN_COMPLETE作爲完成信號,比較少見。

書籍這裏介紹了一些自己建立MDL再返回的,比較稀有。等遇到的時候再專門吧。

明日計劃

開始學習Windows內核漏洞吧,從《0day 安全》 入門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章