檔案系統過濾驅動開發(一)—Win32底層開發小組
宣告:本文無太多新意,只是介紹下學習經驗,大神級人物(如總監大人)請略過,謝謝合作>_<
吐槽一下:學驅動算起來也是從上學期9月份開始吧,之前在家買了<Windows驅動開發技術詳解>這本書,搭了個環境之後,其實也沒碰很多,編了個經典的Hello,World!之後就無太多後續動作,暑假嘛,你們懂的,學習無壓力.上學期斷斷續續算是把基礎啃完了(其實也只是啃完-__-),就到了萬惡的期末考試複習月.寒假開始著手過濾驅動這一塊的學習,然後學期一開始就忙著這個SIG的專案.到現在的感覺就是,思路是有,但是有一些地方找不到切入點,光查資料就花很多時間.現在分享一下我的學習心得,不對之處,敬請指出.
我們小組主要想完成的東西是一個類似SandBoxIE的軟體,就是沙盒,現在360和微軟的瀏覽器當中都加入了沙盒的功能.當然我也沒試用過,因為搜狗有教育網加速- -,我個人對沙盒的理解是對一些操作的重定向,如檔案寫入操作,登錄檔寫入操作,當然更高階的話還要加入記憶體保護.既然有了對寫入操作的重定向,那麼對於讀取操作也應該進行重定向,因為需要讀取修改過的檔案或者登錄檔值.簡單來說,這個沙盒像是一個容器,而我們可以在這個容器裡面執行其他程式,這個程式對我們作業系統做的修改會被重定向,結果是這些修改操作不會對系統有任何的影響,徐志摩同學很好的描述了這一過程:悄悄的我走了,正如我悄悄的來,我揮一揮衣袖,不帶走一片雲彩...再比如在瀏覽器當中加入沙盒功能,假設我們系統訪問了一個被掛馬的網站中了木馬,那麼當瀏覽器關閉的時候,木馬執行的破壞操作都是無效的(當然還是有洩漏帳號密碼的危險,這是另一個話題).
由於驅動開發涉及底層,所以各個版本的Windows都會有所不同,我們針對的作業系統是WindowsXP,對於Windows2K而言,下面有些說的實現方法可能無法實現(底層的支援問題).對於檔案操作的攔截,可以通過檔案系統過濾來實現.現在一般防毒軟體的核心都需要一個檔案過濾驅動,這樣才能實時監控使用者的檔案是否安全.關於磁碟過濾驅動和檔案系統過濾驅動,磁碟過濾驅動比檔案系統過濾驅動更為底層一點,直接過濾磁碟可以實現對硬碟操作的還原,這樣也可以實現我們想要的功能,但是相對來說粒度太大,我們需要的針對特定程式的監控,區分檔案目錄而非無差別的任意磁碟讀寫操作.這一類的軟體有影子系統,雨過天晴多點還原系統.
接下來進入正題,如何開發檔案系統過濾驅動,可能你需要對驅動物件,裝置物件和IRP派遣函式有所瞭解.對於檔案系統過濾驅動的框架而言有兩種選擇,一種是利用Minifilter,這個是檔案系統微過濾驅動,微軟為Windows核心開發者開發了一個新的驅動,叫做過濾管理器,提供一些介面供開發使用者使用,這樣就遮蔽了底層細節,提高了使用者開發的效率,同時也利於在不同系統間的移植和解決相容性問題.在減少程式碼依賴性的同時,由於開發者不需要了解太多的底層細節,只能通過提供的介面進行開發,可能在實現某些特定功能的時候會實現不了.另一種就是傳統型的檔案過濾驅動,微軟提供了Sfilter的例子,細節需要開發者自己實現.出於學習底層知識的目的,我們選用的是Sfilter的框架,雖然比較繁瑣,但是能學的多一點.
過濾驅動的一般思路是繫結相關的裝置物件,之後就能得到該裝置的IRP,進行先手和後手的處理.在檔案系統過濾驅動當中,我們會生成三種裝置:1,過濾驅動自身的控制裝置;2,檔案系統控制裝置的過濾裝置;3,檔案系統卷裝置的過濾裝置.對於過濾驅動自身的控制裝置主要是用來與應用層進行互動,剩下兩個是用來繫結檔案系統的裝置,像FAT32,NTFS這樣的檔案系統主要生成兩類裝置,一種是控制裝置(CDO),一種是卷裝置(VDO),我們需要繫結這兩種裝置.繫結檔案系統的控制裝置我們就可以得到類似卷掛載/解掛載等操作的通知從而進行相應的處理,而繫結卷裝置就能過濾相關的檔案操作.這裡的卷裝置是指檔案系統的卷裝置,卷裝置有兩類,一類是由卷管理器生成的,這類裝置有名字,我們常見的C:,D:是這類卷裝置的符號連結,其裝置名是\Device\harddiskVolume1\,\Device\harddiskVolume2\,這些是由卷裝置管理器生成的,繫結這些裝置無法得到檔案操作的.我們關心的檔案系統卷裝置是沒有名字的,需要通過其他方式獲得.
如何獲取控制裝置並繫結它,可以通過註冊檔案系統變動回撥來實現,原型如下:
1: NTSTATUS
2: IoRegisterFsRegistrationChange(
3: IN PDRIVER_OBJECT DriverObject,
4: IN PDRIVER_FS_NOTIFICATION DriverNotificationRoutine
5: );
回撥函式的原型如下:
1: VOID
2: (*PDRIVER_FS_NOTIFICATION) (
3: IN struct _DEVICE_OBJECT *DeviceObject,
4: IN BOOLEAN FsActive
5: );
通過註冊回撥函式,可以得到系統已經安裝了的檔案系統控制裝置物件也就是*DeviceObject,FsActive是表示檔案系統是啟用還是解除安裝,當檔案系統啟用時我們繫結他,解除安裝則解繫結.檔案系統的回撥函式是用於通知檔案系統的變更,在XP下,當註冊檔案系統回撥之後系統會呼叫回撥函式用以通知已經啟用的檔案系統,而在較早版本下面如Win2K+SP4之前的版本是不會,所以只能將檔案過濾驅動做成靜態載入的形式在系統啟動早期載入.在回撥函式中,我們根據FsActive判斷是繫結還是解繫結:
1: if (FsActive)
2: {
3: LzsbfAttachToFileSystemDevice(pDeviceObject, &DeviceName);
4: }
5: else
6: {
7: LzsbfDetachFromFileSystemDevice(pDeviceObject);
8: }
需要注意的是,這個回撥函式並不通知Raw檔案系統,如果需要關注此類檔案系統需要自己判斷,我們只關心FAT32和NTFS檔案系統,故無視之.
在回撥函式當中,我們還需要加入對檔案系統識別器的判斷.Windows在載入檔案系統驅動的時候並非一開始就載入完整驅動,IO管理器是利用識別器來識別新加入物理介質的檔案系統,當識別成功則解除安裝掉識別器,載入真正的驅動.我們要繫結的是真正的檔案系統驅動裝置,對於部分的檔案系統識別器是由驅動"\FileSystem\Fs_Rec"生成的,注意只是部分,我們對於這部分的裝置通過判斷其驅動物件(識別器是一個裝置物件)的名字,如果是"\FileSystem\Fs_Rec"跳過不進行繫結,對於不是由Fs_Rec產生的識別器,我們在IRP的派遣函式MarjorFunctions[IRP_MJ_FILE_SYSTEM_CONTROL]當中進行處理,我們能夠得到識別器解除安裝要求載入真正檔案系統驅動的通知.
通過檔案系統回撥函式提供的檔案系統控制裝置我們就可以繫結檔案系統的控制裝置,之後根據控制裝置物件得到檔案系統驅動物件,進而可以列舉其相關的卷裝置,然後進行一一繫結.具體的實現程式碼如下:
1: NTSTATUS
2: LzsbfEnumerateFileSystemVolumes(
3: IN PDEVICE_OBJECT pFSDeviceObject,
4: IN PUNICODE_STRING pFSName
5: )
6: {
7: PDEVICE_OBJECT pNewDeviceObject;
8: PLZSBFILTER_DEVICE_EXTENSION pNewDeviceExtension;
9: PDEVICE_OBJECT *DeviceList;
10: PDEVICE_OBJECT pStorageStackDeviceObject;
11: NTSTATUS status;
12: ULONG NumberOfDevice;
13: ULONG i;
14: BOOLEAN IsShadowCopyVolume;
15:
16: PAGED_CODE();
17:
18: status = IoEnumerateDeviceObjectList(pFSDeviceObject->DriverObject,
19: NULL,
20: 0,
21: &NumberOfDevice
22: );
23: if (!NT_SUCCESS(status))
24: {
25: ASSERT(STATUS_BUFFER_TOO_SMALL == status);
26: NumberOfDevice += 8;
27: DeviceList = ExAllocatePoolWithTag(NonPagedPool,
28: (NumberOfDevice * sizeof(PDEVICE_OBJECT)),
29: LZSB_POOL_TAG
30: );
31: if (NULL == DeviceList)
32: {
33: KdPrint(("Fail to allocate DeviceList\n"));
34: return STATUS_INSUFFICIENT_RESOURCES;
35: }
36:
37: status = IoEnumerateDeviceObjectList(pFSDeviceObject->DriverObject,
38: DeviceList,
39: (NumberOfDevice * sizeof(PDEVICE_OBJECT)),
40: &NumberOfDevice
41: );
42: if (!NT_SUCCESS(status))
43: {
44: KdPrint(("Fail to call IoEnumerateDeviceObjectList()\n"));
45: ExFreePool(DeviceList);
46: return status;
47: }
48:
49: for (i = 0; i < NumberOfDevice; i++)
50: {
51: pStorageStackDeviceObject = NULL;
52: __try
53: {
54: if ((DeviceList[i] == pFSDeviceObject) ||
55: (DeviceList[i]->DeviceType != pFSDeviceObject->DeviceType) ||
56: LzsbfIsAttachedToDevice(DeviceList[i], NULL))
57: {
58: KdPrint(("Not to attach this!\n"));
59: __leave;
60: }
61:
62: LzsbfGetBaseDeviceObjectName(DeviceList[i], pFSName);
63: if (pFSName->Length > 0)
64: {
65: KdPrint(("It has a name, do not attach!\n"));
66: __leave;
67: }
68:
69: status = IoGetDiskDeviceObject(DeviceList[i], &pStorageStackDeviceObject);
70: if (!NT_SUCCESS(status))
71: {
72: KdPrint(("Fail to get disk device object!\n"));
73: __leave;
74: }
75:
76: status = LzsbfIsShadowCopyVolume(pStorageStackDeviceObject, &IsShadowCopyVolume);
77: if (NT_SUCCESS(status) && IsShadowCopyVolume)
78: {
79: KdPrint(("It is shadow copy volume, do not attach!\n"));
80: __leave;
81: }
82:
83: status = IoCreateDevice(gLZSBFilterDriverObject,
84: sizeof(LZSBFILTER_DEVICE_EXTENSION),
85: NULL,
86: DeviceList[i]->DeviceType,
87: 0,
88: FALSE,
89: &pNewDeviceObject
90: );
91: if (!NT_SUCCESS(status))
92: {
93: KdPrint(("Fail to create new device!\n"));
94: __leave;
95: }
96:
97: pNewDeviceExtension = pNewDeviceObject->DeviceExtension;
98: pNewDeviceExtension->LZSBDeviceType = FILESYSTEM_VOLUME_DEVICE;
99: pNewDeviceExtension->pStorageStackDeviceObject = pStorageStackDeviceObject;
100: RtlInitEmptyUnicodeString(&pNewDeviceExtension->DeviceName,
101: pNewDeviceExtension->DeviceNameBuffer,
102: sizeof(pNewDeviceExtension->DeviceNameBuffer)
103: );
104: LzsbfGetObjectName(pStorageStackDeviceObject,
105: &pNewDeviceExtension->DeviceName);
106:
107: ExAcquireFastMutex(&gLZSBFilterAttachLock);
108: if (!LzsbfIsAttachedToDevice(DeviceList[i], NULL))
109: {
110: status = LzsbfAttachToMountedDevice(DeviceList[i], pNewDeviceObject);
111: if (!NT_SUCCESS(status))
112: {
113: KdPrint(("Fail to attach volume device\n"));
114: LzsbfCleanupMountedDevice(pNewDeviceObject);
115: IoDeleteDevice(pNewDeviceObject);
116: }
117: }
118: else
119: {
120: KdPrint(("Is attached already\n"));
121: LzsbfCleanupMountedDevice(pNewDeviceObject);
122: IoDeleteDevice(pNewDeviceObject);
123: }
124: ExReleaseFastMutex(&gLZSBFilterAttachLock);
125:
126: }
127: __finally
128: {
129: if (pStorageStackDeviceObject != NULL)
130: {
131: ObDereferenceObject(pStorageStackDeviceObject);
132: }
133: ObDereferenceObject(DeviceList[i]);
134: }
135: }
136: ExFreePool(DeviceList);
137: }
138:
139: KdPrint(("LZSBFilter!lzsbfEnumerateFileSystemVolumes() success!\n"));
140: return STATUS_SUCCESS;
141: }
完成這一過程最主要的呼叫是IoEnumerateDeviceObjectList,原型如下,同樣也是Win2K+SP4和WinXP之後才有的呼叫.這個函式能夠列舉指定驅動的裝置鏈並返回到一個裝置物件指標陣列當中,需要注意的是,此時返回的物件包括檔案系統的控制裝置(我們列舉的是檔案系統驅動的裝置物件),所以在繫結時需要進行判斷是否是控制裝置.
1: NTSTATUS
2: IoEnumerateDeviceObjectList(
3: IN PDRIVER_OBJECT DriverObject,
4: IN PDEVICE_OBJECT *DeviceObjectList,
5: IN ULONG DeviceObjectListSize,
6: OUT PULONG ActualNumberDeviceObjects
7: );
在列舉裝置物件時,除了判斷是否為控制裝置,還需要判斷是否為卷影,卷影是用於磁碟資料恢復的一種特殊裝置,對於卷影我們也是跳過不進行繫結.
稍微總結一下思路:1.註冊檔案系統變動回撥函式;2.在回撥函式中繫結檔案系統的控制裝置,繫結控制裝置時需要注意判斷是否是標準檔案識別器;3.繫結控制裝置之後,列舉檔案系統驅動(通過控制裝置物件得到檔案系統驅動物件指標)的裝置鏈,排除卷影和控制裝置本身,繫結其餘的卷裝置.
至此,大概的繫結裝置思路就有了,剩下的就是一些實現細節,下次再分享..
最後,秉承有圖有碼有真相的原則.附上LOG截圖一張- -
to be continue....