Windows平台核心級檔案存取方法——-fdcwq自CN駭客聯盟)

來源:互聯網
上載者:User
1.背景
在windows平台下,應用程式通常使用API函數來進行檔案訪問,建立,開啟,讀寫檔案。從kernel32的CreateFile/ReadFile/WriteFile函數,到本地系統服務,再到FileSystem及其FilterDriver,經曆了很多層次。在每個層次上,都存在著安全防護軟體,病毒或者後門作監視或者過濾的機會。作為安全產品開發人員,我們需要比別人走得更遠,因此我們需要一個底層的“windows平台核心級檔案訪問”的方法來確保我們能夠看到正確的乾淨的檔案系統。

2.用途
直接的核心層級檔案訪問,在資訊安全領域內有廣泛的用途。用於入侵者的方面,可以讓他繞過殺毒軟體,IDS等安全保護系統的監視。用於檢測者的方面,可以看到一個乾淨的系統,以此來查殺隱藏的後門或者rootkit。用於監控者的方面,則可以瞭解最新的繞過監控的技術,可以根據來設計更新的監控方案。

3.直接存取FSD的核心層級檔案訪問
FSD(FileSystemDriver)層是檔案API函數經過本地系統服務層(native API)最後到達的驅動層次。如果我們可以模仿作業系統,在我們自己的驅動程式裡直接向FSD發送IRP,就可以繞過那些native API 和win32 API了,也就可以繞過設定在這些層次上面的API鉤子等監控措施。

3.1檔案的Create和Open
檔案的Create和Open可以通過發送IRP_MJ_CREATE給FSD,或者調用IoCreateFile函數來完成。Create和Open的區別實際上在於IoCreateFile/IRP_MJ_CREATE的一個參數Disposition的取值。使用IoCreateFile函數的範例代碼:

HANDLE openfile(WCHAR* name,ACCESS_MASK access,ULONG share)  
{
//return 0 for error.
HANDLE hfile;
IO_STATUS_BLOCK iosb;
int stat;
OBJECT_ATTRIBUTES oba;
UNICODE_STRING nameus;
///
if(KeGetCurrentIrql()>PASSIVE_LEVEL){return 0;}
RtlInitUnicodeString(&nameus,name);
InitializeObjectAttributes(&oba,&nameus,OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,0,0);
stat=IoCreateFile(&hfile,access,&oba,&iosb,0,FILE_ATTRIBUTE_NORMAL,share,FILE_OPEN,0,0,0,0,0,0);
if(!NT_SUCCESS(stat)){return 0;}
return hfile;
}

HANDLE createnewfile(WCHAR* name,ACCESS_MASK access,ULONG share)  
{
//return 0 for error.
HANDLE hfile;
IO_STATUS_BLOCK iosb;
int stat;
OBJECT_ATTRIBUTES oba;
UNICODE_STRING nameus;
///
if(KeGetCurrentIrql()>PASSIVE_LEVEL){return 0;}
RtlInitUnicodeString(&nameus,name);
InitializeObjectAttributes(&oba,&nameus,OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,0,0);
stat=IoCreateFile(&hfile,access,&oba,&iosb,0,//AllocationSize this set to 0 that when file opened it was zeroed.
FILE_ATTRIBUTE_NORMAL,share,FILE_OVERWRITE_IF,0,0,0,0,0,0);
if(!NT_SUCCESS(stat)){return 0;}
return hfile;
}

通過發送IRP_MJ_CREATE給FSD的方法與此類似,可以參考IFSDDK document的IRP_MJ_CREATE說明。不同於上面方法的是需要自己建立一個FILE_OBJECT,好於上面方法的是這種方法不需要一個HANDLE,HANDLE是線程依賴的,FileObject則是線程無關。

3.2檔案的Read和Write
我們通過給FSD發送IRP_MJ_READ來讀取檔案,給FSD發送IRP_MJ_WRITE來改寫檔案。
如果我們是通過一個HANDLE來執行(如使用IoCreateFile開啟的檔案),就要先用ObReferenceObjectByHandle函數來獲得這個Handle對應的FileObject。我們只能給FileObject發送IRP。

stat=ObReferenceObjectByHandle(handle,GENERIC_READ,*IoFileObjectType,KernelMode,(PVOID*)&fileob,0);

之後我們使用IoAllocateIrp分配一個IRP。根據FileObject->DeviceObject->Flags的值,我們判斷目標檔案系統使用什麼樣的IO方式。

if(fileob->DeviceObject->Flags & DO_BUFFERED_IO)
{
irp->AssociatedIrp.SystemBuffer=buffer;//buffered io
}
else if(fileob->DeviceObject->Flags & DO_DIRECT_IO)
{
mdl=IoAllocateMdl(buffer,count,0,0,0);
MmBuildMdlForNonPagedPool(mdl);
irp->MdlAddress=mdl;//direct io
}
else
{
irp->UserBuffer=buffer;//neither i/o, use kernel buffer
}

對每種不同的IO方式使用不同的地址傳遞方式。隨後我們填充IRP內的各個參數域,就可以發送IRP了。以Read為例:

irpsp->FileObject=fileob;
irpsp->MajorFunction=IRP_MJ_READ;
irpsp->MinorFunction=IRP_MN_NORMAL;//0
irpsp->Parameters.Read.ByteOffset=offsetused;
irpsp->Parameters.Read.Key=0;
irpsp->Parameters.Read.Length=count;

接著要考慮如果IRP不能及時完成,會非同步返回的情況,我們安裝一個CompletionRoutine,在CompletionRoutine裡面設定一個事件為已啟用,通知我們的主線程讀取或者寫入操作已經完成。

IoSetCompletionRoutine(irp,IoCompletion,&event,1,1,1);

NTSTATUS
IoCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context
)
{
KeSetEvent((PRKEVENT)Context, IO_DISK_INCREMENT, 0);
return STATUS_MORE_PROCESSING_REQUIRED;
}

現在可以發送IRP了。如果不採取特殊的措施的話,IRP發送目標是FileObject對應的DeviceObject。發送後,等待IRP的完成並且釋放資源,返回。

stat=IoCallDriver(fileob->DeviceObject,irp);
if(stat==STATUS_PENDING){
KeWaitForSingleObject(&event, Executive,KernelMode,0,0);
stat=irp->IoStatus.Status;
}
if(!NT_SUCCESS(stat))
{
IoFreeIrp(irp);
if(mdl){IoFreeMdl(mdl);}//if DO_DIRECT_IO
return -1;
}
stat=irp->IoStatus.Information;//bytes read
IoFreeIrp(irp);
if(mdl){IoFreeMdl(mdl);}//if DO_DIRECT_IO
return stat;

3.3檔案的Delete
Delete實際上是通過向FSD發送IRP_MJ_SET_INFORMATION的IRP,並把IrpSp->Parameters.SetFile.FileInformationClass設定為FileDispositionInformation,用一個FILE_DISPOSITION_INFORMATION結構填充buffer來執行的。

fdi.DeleteFile=TRUE;

irpsp->MajorFunction=IRP_MJ_SET_INFORMATION;
irpsp->Parameters.SetFile.Length = sizeof(FILE_DISPOSITION_INFORMATION);
irpsp->Parameters.SetFile.FileInformationClass = FileDispositionInformation;  
irpsp->Parameters.SetFile.DeleteHandle = (HANDLE)handle;  

3.4檔案的Rename
類似於Delete,Rename是向FSD發送IRP_MJ_SET_INFORMATION的IRP,把IrpSp->Parameters.SetFile.FileInformationClass設定為FileRenameInformation,填充buffer為FILE_RENAME_INFORMATION結構。

fri.ReplaceIfExists=TRUE;
fri.RootDirectory=0;//Set fri.FileName to full path name.
fri.FileNameLength=wcslen(filename)*2;
wcscpy(fri.FileName,filename);//If the RootDirectory member is NULL, and the file is being moved to a different directory, this member specifies the full pathname to be assigned to the file.  

irpsp->MajorFunction=IRP_MJ_SET_INFORMATION;
irpsp->Parameters.SetFile.Length = sizeof(FILE_FILE_RENAME_INFORMATION);
irpsp->Parameters.SetFile.FileInformationClass = FileRenameInformation;  

綜上,於是我們可以在驅動裡面通過發送IRP來直接存取檔案系統了,繞過了native API 和win32 API層次。

4.繞過檔案系統過濾驅動和鉤子

有了第三部分的內容,我們目前可以直接給FSD發送請求操作檔案。但是這還不夠,因為有很多的殺毒軟體或者監視工具使用FSD Filter Driver或者FSD Hook的辦法來監控檔案操作。在今天這篇文章裡我講一些原理性的東西,提供繞過FSD Filter Driver / FSD Hook的思路。

4.1對付檔案系統過濾驅動

檔案系統過濾驅動Attach在正常的檔案系統之上,監視和過濾我們的檔案訪問。檔案系統驅動棧就是由這一連串的Attach起來的過濾驅動組成。我們可以用IoGetRelatedDeviceObject這個函數來獲得一個FileObject對應的最底層的那個功能驅動對象(FDO)。但是這樣雖然繞過了那些過濾驅動,卻同時也繞過了正常的FSD如Ntfs/Fastfat,因為正常的FSD也是作為一個過濾驅動存在的。磁碟檔案對象的對應的最底層的FDO是Ftdisk.sys,它已經因為過於底層而不能處理我們投遞的IRP請求。
其實正常的FSD資訊儲存在一個Vpb結構中,我們可以使用IoGetBaseFileSystemDeviceObject這個未公開的核心功能來得到它。它就是我們發送IRP的目標了。

4.2對付替換DispatchRoutine的FSD Hook

這是一種常用的FSD Hook方式。我們需要得到原本的DispatchRoutine,向原本的DispatchRoutine發送我們的IRP。這裡提供一個思路:我們可以讀取原本FSD驅動的.INIT段或者.TEXT段,尋找其DriverEntry函數,在它的DriverEntry函數中肯定設定了自己的DriverObject的各個DispatchRoutine。在這個函數中我們就能找到我們想要的DispatchRoutine的地址。只需要使用特徵碼搜尋的方法就可以搜尋到這個值。

4.3對付Inline Hook DispatchRoutine函數本身的FSD Hook

這種Hook方法比較狠毒,但不是非常常見於安全產品中,一般應用在木馬和rootkit上,比如我自己寫的rootkit。它沒有更改DriverObject裡面的DispatchRoutine的函數指標,而是向函數開頭寫入彙編指令的JMP來跳轉函數。對付它的基本思路就是讀取存在磁碟上的FSD的檔案,載入到記憶體一份乾淨的備份,察看我們要調用的DispatchRoutine開頭的幾個位元組和這個乾淨備份是否一致。如果不一致,尤其是存在JMP,RET,INT3一類的彙編指令的時候,很可能就是存在了Inline Hook。(但要充分考慮重定位的情況。)如果存在Inline Hook,我們就把乾淨的函數開頭拷貝過來覆蓋掉被感染的函數頭。然後在發送IRP,就不會被Inline Hook監視或篡改了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.