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_ATTRIBUTES
和oa
变量的定义是什么?
@Saulo 这只是静态初始化的宏(我自己的)OBJECT_ATTRIBUTES
当然这是硬编码路径,在实际应用中 - 您将在运行时自己初始化它。现在 - 你自己很难使用例如初始化简单的结构吗?但是我使用您已经看到的#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
内部宏。并建议不要复制/粘贴此内容,但要理解。如果看不懂 - 不使用,这是可选的。
@Saulo - and oa variable
- 我调用ntTraverse(&oa,..
函数的第一个参数被声明为POBJECT_ATTRIBUTES poa
- 那么oa
类型是什么?很难猜出STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot");
是什么?这不是函数的一部分,而是演示的一部分使用 hardcoded 路径 \\systemroot
调用此函数 - 在所有情况下,您都将是动态路径或自己硬编码,你怎么能
在你的宏中 oa
的类型是 OBJECT_ATTRIBUTES
并且已经被声明了,对吧?所以,没有必要在ntTraverse();
函数内部声明:-)
@Saulo - 您可以自己使用RtlInitUnicodeString
+ InitializeObjectAttributes
,而不是STATIC_OBJECT_ATTRIBUTES
(此宏硬代码名称,仅供演示/测试使用)以上是关于Windows 驱动内核:如何枚举所有子目录和文件?的主要内容,如果未能解决你的问题,请参考以下文章