Windows 驱动内核:如何枚举所有子目录和文件?

Posted

技术标签:

【中文标题】Windows 驱动内核:如何枚举所有子目录和文件?【英文标题】:Windows driver kernel: How enumerate all subdirectories and files? 【发布时间】:2017-01-25 14:57:30 【问题描述】:

我正在使用一个小型 antirootkit,我需要添加一个功能:

删除 rootkit 目录和您可能的子目录中的所有文件。

那么,首先有必要知道所有这些目录和文件,对吧?

对此,我下面的代码已经完成了这项任务的一半。他枚举了特定目录的所有目录和文件,但没有“查看”子目录(文件和文件夹)。

例如:

输出:

代码:

#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;
    

那么,这是怎么做到的呢?

提前感谢任何帮助或建议。


------------------------------------------ - - - - - - 编辑: - - - - - - - - - - - - - - - - - - --------------------------------

我找到了一个可能的解决方案,但我在这一行得到了一个 BSOD

if ( (*pDir)->NextEntryOffset)

KernelFindNextFile 方法中。

有什么建议吗?

这是我找到的代码:

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

蓝屏:

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: 

【问题讨论】:

一般来说,你需要一个函数,它以目录名作为参数,然后对于目录中遇到的每个子目录,构造路径名,并使用构造的路径递归调用自身。 如果这是 Linux,我会说“小心与祖先节点的硬链接”——这将是 rootkit 炸毁你的好方法。 rootkit也许可以对连接点和挂载点做类似的事情。 可能最好避免内核驱动程序中的递归。堆栈空间可能非常有限(在某些情况下),如果您确实溢出堆栈,就会发生坏事。 (我的意思是,比在用户模式下更是如此!)但是将递归代码转换为非递归代码只是基本的编程,你不需要做任何特别的事情,因为它是一个内核驱动程序。 【参考方案1】:

好的,这里是测试工作的代码。如果有人不能使用它或得到 BSOD - 可能不是代码问题,而是某些技能问题

几个注意事项 - 如果您有以前的模式内核 - 使用 Nt* api(导出时)而不是 Zw* api。或Io* api。如果你不明白为什么,或者你以前的模式是什么 - 最好不要尝试在内核中编程。

强制使用FILE_OPEN_REPARSE_POINT选项,如果不明白这是什么以及为什么需要使用,甚至不尝试运行此代码

用于删除 - 使用 FILE_DELETE_ON_CLOSE 选项打开文件,仅用于转储 - 使用 FILE_DIRECTORY_FILE 选项。

代码自己使用 c:\Users - 所以这对内核来说是可以的,但请始终使用 IoGetRemainingStackSize 检查堆栈空间

如果你自己不能做到这一点,我不会纠正你代码中的每个逗号

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

【讨论】:

非常感谢!但是您对ntTraverse() 中的STATIC_OBJECT_ATTRIBUTESoa 变量的定义是什么? @Saulo 这只是静态初始化的宏(我自己的)OBJECT_ATTRIBUTES 当然这是硬编码路径,在实际应用中 - 您将在运行时自己初始化它。现在 - 你自己很难使用例如初始化简单的结构吗?但是我使用您已经看到的#define STATIC_OBJECT_ATTRIBUTES(oa, name)\ STATIC_UNICODE_STRING(label(m), name);\ static OBJECT_ATTRIBUTES oa = sizeof oa, 0, (PUNICODE_STRING)&amp;label(m), OBJ_CASE_INSENSITIVE 内部宏。并建议不要复制/粘贴此内容,但要理解。如果看不懂 - 不使用,这是可选的。 @Saulo - and oa variable - 我调用ntTraverse(&amp;oa,.. 函数的第一个参数被声明为POBJECT_ATTRIBUTES poa - 那么oa 类型是什么?很难猜出STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot"); 是什么?这不是函数的一部分,而是演示的一部分使用 hardcoded 路径 \\systemroot 调用此函数 - 在所有情况下,您都将是动态路径或自己硬编码,你怎么能 在你的宏中 oa 的类型是 OBJECT_ATTRIBUTES 并且已经被声明了,对吧?所以,没有必要在ntTraverse();函数内部声明:-) @Saulo - 您可以自己使用RtlInitUnicodeString + InitializeObjectAttributes,而不是STATIC_OBJECT_ATTRIBUTES(此宏硬代码名称,仅供演示/测试使用)

以上是关于Windows 驱动内核:如何枚举所有子目录和文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何给开发好的Windows内核驱动签名?双证书。ssl证书.

windows常用系统命令

Windows常用命令汇总以及基础知识

X64驱动:内核里操作进程

Python提取Linux内核源代码的目录结构

windows内核文件在哪