4

I'm working in a small antirootkit and i need add a functionality that is:

  • Delete all files on directory of rootkit and in yours possible subdiretories.

So, firstly is necessary know all these directories and files, right?

To this, i have code below that already make half of this task. He enumerate all directories and files of a specific directory but not "see" subdirectories ( files and folders).

Eg:

enter image description here

Output:

enter image description here

Code:

#include <ntifs.h>

 typedef unsigned int UINT;

    NTSTATUS EnumFilesInDir()
    {

        HANDLE hFile = NULL;
        UNICODE_STRING szFileName = { 0 };
        OBJECT_ATTRIBUTES Oa = { 0 };
        NTSTATUS ntStatus = 0;
        IO_STATUS_BLOCK Iosb = { 0 };
        UINT uSize = sizeof(FILE_BOTH_DIR_INFORMATION);
        FILE_BOTH_DIR_INFORMATION *pfbInfo = NULL;
        BOOLEAN bIsStarted = TRUE;

        RtlInitUnicodeString(&szFileName, L"\\??\\C:\\MyDirectory"); 
        InitializeObjectAttributes(&Oa, &szFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
        ntStatus = ZwCreateFile(&hFile, GENERIC_READ | SYNCHRONIZE, &Oa, &Iosb, 0, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
        if (!NT_SUCCESS(ntStatus)) { return ntStatus; }
        pfbInfo = ExAllocatePoolWithTag(PagedPool, uSize, '0000');
        if (pfbInfo == NULL)
        {
            ZwClose(hFile); return STATUS_NO_MEMORY;
        }
        while (TRUE)
        {
        lbl_retry:
            RtlZeroMemory(pfbInfo, uSize);
            ntStatus = ZwQueryDirectoryFile(hFile, 0, NULL, NULL, &Iosb, pfbInfo, uSize, FileBothDirectoryInformation, FALSE, NULL, bIsStarted);
            if (STATUS_BUFFER_OVERFLOW == ntStatus) {
                ExFreePoolWithTag(pfbInfo, '000');
                uSize = uSize * 2;
                pfbInfo = ExAllocatePoolWithTag(PagedPool, uSize, '0000');
                if (pfbInfo == NULL) { ZwClose(hFile); return STATUS_NO_MEMORY; }
                goto lbl_retry;
            }
            else if (STATUS_NO_MORE_FILES == ntStatus)
            {
                ExFreePoolWithTag(pfbInfo, '000');
                ZwClose(hFile); return STATUS_SUCCESS;
            }
            else if (STATUS_SUCCESS != ntStatus)
            {
                ExFreePoolWithTag(pfbInfo, '000');
                ZwClose(hFile);
                return ntStatus;
            }
            if (bIsStarted)
            {
                bIsStarted = FALSE;
            }
            while (TRUE)
            {
                WCHAR * szWellFormedFileName = ExAllocatePoolWithTag(PagedPool, (pfbInfo->FileNameLength + sizeof(WCHAR)), '0001');
                if (szWellFormedFileName)
                {
                    RtlZeroMemory(szWellFormedFileName, (pfbInfo->FileNameLength + sizeof(WCHAR)));
                    RtlCopyMemory(szWellFormedFileName, pfbInfo->FileName, pfbInfo->FileNameLength);
                    //KdPrint(("File name is: %S\n", szWellFormedFileName));
                    KdPrint((" %S\n", szWellFormedFileName));
                    ExFreePoolWithTag(szWellFormedFileName, '000');
                }
                if (pfbInfo->NextEntryOffset == 0) { break; }
                pfbInfo += pfbInfo->NextEntryOffset;
            }
        }
        ZwClose(hFile);
        ExFreePoolWithTag(pfbInfo, '000');
        return ntStatus;
    }

So, how do this?

Thank in advance by any help or suggestion.


--------------------------------------------------------EDIT:--------------------------------------------------------------------

I found a possible solution, but i'm getting a BSOD in this line:

if ( (*pDir)->NextEntryOffset)

In KernelFindNextFile method.

Some suggestion?

Here is the code that i found:

#include <ntifs.h>
#include <stdlib.h>

HANDLE KernelCreateFile(IN PUNICODE_STRING pstrFile,IN BOOLEAN bIsDir)
{
    HANDLE hFile = NULL;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    IO_STATUS_BLOCK StatusBlock = {0};
    ULONG ulShareAccess = FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE;
    ULONG ulCreateOpt = FILE_SYNCHRONOUS_IO_NONALERT;

    OBJECT_ATTRIBUTES objAttrib = {0};
    ULONG ulAttributes = OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE;
    InitializeObjectAttributes(&objAttrib,pstrFile,ulAttributes,NULL,NULL);

    ulCreateOpt |= bIsDir?FILE_DIRECTORY_FILE:FILE_NON_DIRECTORY_FILE;
    Status = ZwCreateFile(
        &hFile,
        GENERIC_ALL,
        &objAttrib,
        &StatusBlock,
        0,
        FILE_ATTRIBUTE_NORMAL,
        ulShareAccess,
        FILE_OPEN_IF,
        ulCreateOpt,
        NULL,
        0);
    if (!NT_SUCCESS(Status))
    {
        return (HANDLE)-1;
    }
    return hFile;
}

PFILE_BOTH_DIR_INFORMATION KernelFindFirstFile(IN HANDLE hFile,IN ULONG ulLen,OUT PFILE_BOTH_DIR_INFORMATION pDir)
{
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    IO_STATUS_BLOCK StatusBlock = {0};
    PFILE_BOTH_DIR_INFORMATION pFileList = (PFILE_BOTH_DIR_INFORMATION)ExAllocatePool(PagedPool,ulLen);
    Status = ZwQueryDirectoryFile(
        hFile,NULL,NULL,NULL,
        &StatusBlock,
        pDir,
        ulLen,
        FileBothDirectoryInformation,
        TRUE,
        NULL,
        FALSE);
    RtlCopyMemory(pFileList,pDir,ulLen);
    Status = ZwQueryDirectoryFile(
        hFile,NULL,NULL,NULL,
        &StatusBlock,
        pFileList,
        ulLen,
        FileBothDirectoryInformation,
        FALSE,
        NULL,
        FALSE);
    return pFileList;
}

NTSTATUS KernelFindNextFile(IN OUT PFILE_BOTH_DIR_INFORMATION* pDir)
{
    if ( (*pDir)->NextEntryOffset)
    {
        (*pDir)=(PFILE_BOTH_DIR_INFORMATION)((UINT32)(*pDir)+(*pDir)->NextEntryOffset); 
        return STATUS_SUCCESS;
    }
    return STATUS_UNSUCCESSFUL;
}

void Traversal()
{
    UNICODE_STRING ustrFolder = {0};
    WCHAR szSymbol[0x512] = L"\\??\\";
    UNICODE_STRING ustrPath = RTL_CONSTANT_STRING(L"C:\\MyDirectory");
    HANDLE hFile = NULL;
    SIZE_T nFileInfoSize = sizeof(FILE_BOTH_DIR_INFORMATION)+270*sizeof(WCHAR);
    SIZE_T nSize = nFileInfoSize*0x256;
    char strFileName[0x256] = {0};
    PFILE_BOTH_DIR_INFORMATION pFileListBuf = NULL;
    PFILE_BOTH_DIR_INFORMATION pFileList = NULL;
    PFILE_BOTH_DIR_INFORMATION pFileDirInfo = (PFILE_BOTH_DIR_INFORMATION)ExAllocatePool(PagedPool,nSize);

    wcscat_s(szSymbol,_countof(szSymbol),ustrPath.Buffer);
    RtlInitUnicodeString(&ustrFolder,szSymbol);
    hFile = KernelCreateFile(&ustrFolder,TRUE);
    pFileList = pFileListBuf;
    KernelFindFirstFile(hFile,nSize,pFileDirInfo);
    if (pFileList)
    {
        RtlZeroMemory(strFileName,0x256);
        RtlCopyMemory(strFileName,pFileDirInfo->FileName,pFileDirInfo->FileNameLength);
        if (strcmp(strFileName,"..")!=0 || strcmp(strFileName,".")!=0)
        {
            if (pFileDirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            {
                DbgPrint("[Directory]%S\n",strFileName);
            }
            else
            {
                DbgPrint("[File]%S\n",strFileName);
            }
        }
    }
    while (NT_SUCCESS(KernelFindNextFile(&pFileList)))
    {
        RtlZeroMemory(strFileName,0x256);
        RtlCopyMemory(strFileName,pFileList->FileName,pFileList->FileNameLength);
        if (strcmp(strFileName,"..")==0 || strcmp(strFileName,".")==0)
        {
            continue;
        }
        if (pFileList->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            DbgPrint("[Directory]%S\n",strFileName);
        } 
        else
        {
            DbgPrint("[File]%S\n",strFileName);
        }
    }
    RtlZeroMemory(strFileName,0x256);
    RtlCopyMemory(strFileName,pFileListBuf->FileName,pFileListBuf->FileNameLength);
    if (strcmp(strFileName,"..")!=0 || strcmp(strFileName,".")!=0)
    {
        if (pFileDirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            DbgPrint("[Directory]%S\n",strFileName);
        }
        else
        {
            DbgPrint("[File]%S\n",strFileName);
        }
        ExFreePool(pFileListBuf);
        ExFreePool(pFileDirInfo);
    }
}

BSOD:

FAULTING_SOURCE_LINE_NUMBER:  263

FAULTING_SOURCE_CODE:  
   259: }
   260: 
   261: NTSTATUS KernelFindNextFile(IN OUT PFILE_BOTH_DIR_INFORMATION* pDir)
   262: {
>  263:     if ((*pDir)->NextEntryOffset)
   264:     {
   265:         (*pDir) = (PFILE_BOTH_DIR_INFORMATION)((UINT32)(*pDir) + (*pDir)->NextEntryOffset);
   266:         return STATUS_SUCCESS;
   267:     }
   268: 
  • 1
    In general you need a function which takes a directory name as argument, and then for every subdirectory it encounters in the directory, constructs the path name, and recursively calls itself with the constructed path. – Martin Bonner supports Monica Jan 25 '17 at 15:05
  • 1
    If this was Linux, I would say "beware hard links to ancestor nodes" - which would be nice way for the rootkit to blow you up. The rootkit *may* be able to do something similar with junctions and mount points. – Martin Bonner supports Monica Jan 25 '17 at 15:08
  • Probably best to avoid recursion in a kernel driver. Stack space can be extremely limited (in some cases) and if you do overflow the stack bad things happen. (Even more so than in user mode, I mean!) But converting recursive code into non-recursive code is just basic programming, you don't need to do anything special just because it is a kernel driver. – Harry Johnston Jan 25 '17 at 21:45

1 Answers1

2

ok, here code which tested and works. if somebody can not use it or got BSOD - probably problem not in code but in somebody skills

several notes - if you have previous mode kernel - use Nt* api (when exported) but not Zw* api. or Io* api. if you not understand why, or what is your previous mode - better even not try programming in kernel.

mandatory use FILE_OPEN_REPARSE_POINT option or even not try run this code if not understand what is this and why need use

for delete - open files with FILE_DELETE_ON_CLOSE option, for dump only - with FILE_DIRECTORY_FILE option instead.

code yourself used <= 0x1800 bytes of stack in x64 in deepest folders, like c:\Users - so this is ok for kernel, but always check stack space with IoGetRemainingStackSize

i will be not correct every comma in your code, if you can not do this yourself

#define ALLOCSIZE PAGE_SIZE

#ifdef _REAL_DELETE_
#define USE_DELETE_ON_CLOSE FILE_DELETE_ON_CLOSE
#define FILE_ACCESS FILE_GENERIC_READ|DELETE
#else
#define USE_DELETE_ON_CLOSE FILE_DIRECTORY_FILE
#define FILE_ACCESS FILE_GENERIC_READ
#endif


// int nLevel, PSTR prefix for debug only
void ntTraverse(POBJECT_ATTRIBUTES poa, ULONG FileAttributes, int nLevel, PSTR prefix)
{
    if (IoGetRemainingStackSize() < PAGE_SIZE)
    {
        DbgPrint("no stack!\n");
        return ;
    }

    if (!nLevel)
    {
        DbgPrint("!nLevel\n");
        return ;
    }

    NTSTATUS status;
    IO_STATUS_BLOCK iosb;
    UNICODE_STRING ObjectName;
    OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName };

    DbgPrint("%s[<%wZ>]\n", prefix, poa->ObjectName);

#ifdef _REAL_DELETE_
    if (FileAttributes & FILE_ATTRIBUTE_READONLY)
    {
        if (0 <= NtOpenFile(&oa.RootDirectory, FILE_WRITE_ATTRIBUTES, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_OPEN_FOR_BACKUP_INTENT|FILE_OPEN_REPARSE_POINT))
        {
            static FILE_BASIC_INFORMATION fbi = { {}, {}, {}, {}, FILE_ATTRIBUTE_NORMAL };
            NtSetInformationFile(oa.RootDirectory, &iosb, &fbi, sizeof(fbi), FileBasicInformation);
            NtClose(oa.RootDirectory);
        }
    }
#endif//_REAL_DELETE_

    if (0 <= (status = NtOpenFile(&oa.RootDirectory, FILE_ACCESS, poa, &iosb, FILE_SHARE_VALID_FLAGS, 
        FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|USE_DELETE_ON_CLOSE)))
    {
        if (FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            if (PVOID buffer = ExAllocatePool(PagedPool, ALLOCSIZE))
            {
                union {
                    PVOID pv;
                    PBYTE pb;
                    PFILE_DIRECTORY_INFORMATION DirInfo;
                };

                while (0 <= (status = NtQueryDirectoryFile(oa.RootDirectory, NULL, NULL, NULL, &iosb, 
                    pv = buffer, ALLOCSIZE, FileDirectoryInformation, 0, NULL, FALSE)))
                {

                    ULONG NextEntryOffset = 0;

                    do 
                    {
                        pb += NextEntryOffset;

                        ObjectName.Buffer = DirInfo->FileName;

                        switch (ObjectName.Length = (USHORT)DirInfo->FileNameLength)
                        {
                        case 2*sizeof(WCHAR):
                            if (ObjectName.Buffer[1] != '.') break;
                        case sizeof(WCHAR):
                            if (ObjectName.Buffer[0] == '.') continue;
                        }

                        ObjectName.MaximumLength = ObjectName.Length;

#ifndef _REAL_DELETE_
                        if (DirInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
#endif
                        {
                            ntTraverse(&oa, DirInfo->FileAttributes, nLevel - 1, prefix - 1);
                        }
#ifndef _REAL_DELETE_
                        else
#endif
                        {
                            DbgPrint("%s%8I64u <%wZ>\n", prefix, DirInfo->EndOfFile.QuadPart, &ObjectName);
                        }

                    } while (NextEntryOffset = DirInfo->NextEntryOffset);
                }

                ExFreePool(buffer);

                if (status == STATUS_NO_MORE_FILES)
                {
                    status = STATUS_SUCCESS;
                }
            }
        }

        NtClose(oa.RootDirectory);
    }

    if (0 > status)
    {
        DbgPrint("---- %x %wZ\n", status, poa->ObjectName);
    }
}

void ntTraverse()
{
    char prefix[MAXUCHAR + 1];
    memset(prefix, '\t', MAXUCHAR);
    prefix[MAXUCHAR] = 0;

    STATIC_OBJECT_ATTRIBUTES(oa, "\\??\\c:\\users");
    //STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot");
    ntTraverse(&oa, FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_READONLY, MAXUCHAR, prefix + MAXUCHAR);
}
isanae
  • 3,253
  • 1
  • 22
  • 47
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • thank you very much! but what's your definition for `STATIC_OBJECT_ATTRIBUTES` and `oa` variable in `ntTraverse()`? –  Jan 26 '17 at 01:49
  • @Saulo this is only macro (my own) for static initialize `OBJECT_ATTRIBUTES` of course this is hardcode path, in real application - you will be initialize it in runtime yourself. and now - are hard yourself use for example initialize simply structure ? however I use`#define STATIC_OBJECT_ATTRIBUTES(oa, name)\ STATIC_UNICODE_STRING(label(m), name);\ static OBJECT_ATTRIBUTES oa = { sizeof oa, 0, (PUNICODE_STRING)&label(m), OBJ_CASE_INSENSITIVE } ` internal macros you already seen. and advice not copy/paste this but understand. if can not understand - not use, this is optional. – RbMm Jan 26 '17 at 02:18
  • @Saulo - `and oa variable` - I call `ntTraverse(&oa,..` first parameter of function is declared as `POBJECT_ATTRIBUTES poa` - so what is `oa` type ? so hard guess what is `STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot");` for example ? and this is not part of function, but part of demo call this function with **hardcoded** path `\\systemroot` - you in all case will be dynamic paths or hardcode it yourself, how you can – RbMm Jan 26 '17 at 02:23
  • In your macro `oa` is of type `OBJECT_ATTRIBUTES` and already is declared, right? So, no is necessary declare inside of `ntTraverse();` function :-) –  Jan 26 '17 at 02:31
  • @Saulo - you can use `RtlInitUnicodeString` + `InitializeObjectAttributes` yourself, instead `STATIC_OBJECT_ATTRIBUTES` (this macro **hardcode** name, for demo/test use only) – RbMm Jan 26 '17 at 02:38
  • Okay, understood! :-) –  Jan 26 '17 at 02:44
  • with this code above i'm able to delete only simple folders. If i want delete a folder of a rootkit and that this folder is protected by rootkit driver, how do this? thank you. –  May 30 '17 at 23:55
  • @Saulo - windows not have term "simply" folder. this code correct delete folder, when this is possible. if say some file is have mapped section on it - impossible delete file, until section will be not closed. so delete folder in this case impossible. rootkit this is at all absolute another topic. can not be geneal solution in this case. what if rootkit filter FS request ? and fail say `NtOpenFile` on it folder ? we already stopped here – RbMm May 31 '17 at 00:03
  • `if say some file is have mapped section on it - impossible delete file, until section will be not closed. so delete folder in this case impossible.` But like you know, exists anti rootkit softwares that are able to delete any protected folders/files. How this is made? for example _PC Hunter_ is a good antirootkit and is able to make this task :-) –  May 31 '17 at 00:15