Windows下内核文件隐藏技术
为防止此代码应用于一些触犯法律方面考虑,本篇文章仅讲述基本的逻辑方法,完整的代码将不会在文章中展示。
首先,我们要了解系统或者程序枚举文件的原理,它内部基本上就是调用查询API,然后在内核中会收到相关请求的IRP。
所以,我们要实现文件隐藏的话,基本上思路是有两个的:
1、阻断IRP请求,不允许查询。这种情况一般是隐藏整个目录使用的,而且这种方式波及的范围有点广。
2、正常处理IRP请求,但是我们需要对返回的结果进行处理,即从返回结果中删除我们要隐藏的文件。这种情况类似于hook,但是比hook更加的隐蔽、安全。
实现文件隐藏我们使用的是MiniFilter,这是微软提供给我们的内核驱动框架,所以还是非常安全的。逻辑非常简单,我们只需要注册 IRP_MJ_DIRECTORY_CONTROL 请求的回调函数处理返回结果即可。
一、注册相关回调函数
注册回调代码如下:
CONST FLT_OPERATION_REGISTRATION Callbacks[]={//文件隐藏{IRP_MJ_DIRECTORY_CONTROL,0,NULL,PostDirectoryControl,},{IRP_MJ_OPERATION_END}};二、编写回调函数
回调函数的伪代码如下:
#defineDIR_ENTRY_NEXT_OFFSET(Entry)(*(PULONG)(Entry))#defineDIR_ENTRY_SET_NEXT_OFFSET(Entry,Value)(*(PULONG)(Entry)=(Value))FLT_POSTOP_CALLBACK_STATUSPostDirectoryControl(_Inout_ PFLT_CALLBACK_DATA Data,_In_ PCFLT_RELATED_OBJECTS FltObjects,_In_opt_ PVOID CompletionContext,_In_ FLT_POST_OPERATION_FLAGS Flags){ULONG nextOffset=0;BOOL removedAllEntries=TRUE;UNICODE_STRING strFilePathName;PFLT_FILE_NAME_INFORMATION nameInfo;PCHAR currentFileInfo=NULL;PCHAR blinkFileInfo=NULL;PCHAR previousFileInfo=NULL;LPWSTR pCurrentDir=NULL;UNREFERENCED_PARAMETER(FltObjects);UNREFERENCED_PARAMETER(CompletionContext);UNREFERENCED_PARAMETER(Data);UNREFERENCED_PARAMETER(Flags);if(FltObjects==NULL||FltObjects->FileObject==NULL||Data->Iopb->MinorFunction!=IRP_MN_QUERY_DIRECTORY||FlagOn(Flags,FLTFL_POST_OPERATION_DRAINING)){returnFLT_POSTOP_FINISHED_PROCESSING;}if(Data->Iopb->Parameters.DirectoryControl.QueryDirectory.Length<=0||!NT_SUCCESS(Data->IoStatus.Status)){returnFLT_POSTOP_FINISHED_PROCESSING;}//数据长度ULONG dataLength=Data->Iopb->Parameters.DirectoryControl.QueryDirectory.Length;//获取目录/文件的相关数据if(Data->Iopb->Parameters.DirectoryControl.QueryDirectory.MdlAddress!=NULL){currentFileInfo=(PCHAR)MmGetSystemAddressForMdlSafe(Data->Iopb->Parameters.DirectoryControl.QueryDirectory.MdlAddress,NormalPagePriority);}else{currentFileInfo=(PCHAR)Data->Iopb->Parameters.DirectoryControl.QueryDirectory.DirectoryBuffer;}if(currentFileInfo==NULL)gotoFINAL;previousFileInfo=currentFileInfo;//保存下来blinkFileInfo=currentFileInfo;ULONG allOffsetLength=0;//循环遍历所有的数据while(TRUE){//距离下一个节点的偏移量nextOffset=DIR_ENTRY_NEXT_OFFSET(previousFileInfo);//匹配隐藏文件if(FindHideFile(Data,previousFileInfo,&pCurrentDir)){//判断是不是最后一个节点if(nextOffset==0){DIR_ENTRY_NEXT_OFFSET(blinkFileInfo)=0;}else{DIR_ENTRY_NEXT_OFFSET(blinkFileInfo)+=nextOffset;FltSetCallbackDataDirty(Data);}}else{removedAllEntries=FALSE;blinkFileInfo=previousFileInfo;}allOffsetLength+=nextOffset;previousFileInfo+=nextOffset;if(nextOffset==0||allOffsetLength>=dataLength)break;}FINAL:if(pCurrentDir){ExFreePoolWithTag(pCurrentDir,MINIFILTER_TAG);pCurrentDir=NULL;}returnFLT_POSTOP_FINISHED_PROCESSING;}查找隐藏文件的代码如下,部分代码已隐藏,以备注代替:
//查找是否是隐藏文件BOOLFindHideFile(_Inout_ PFLT_CALLBACK_DATA pData,PCHAR pActual,LPWSTR*pCurrentDir){BOOL isRet=FALSE;DWORD fileNameLength=0;LPCWSTR pFileName=NULL;if(pData==NULL||pActual==NULL)gotoFINAL;switch(pData->Iopb->Parameters.DirectoryControl.QueryDirectory.FileInformationClass){caseFileDirectoryInformation:fileNameLength=((PFILE_DIRECTORY_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_DIRECTORY_INFORMATION)pActual)->FileName;break;caseFileFullDirectoryInformation:fileNameLength=((PFILE_FULL_DIR_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_FULL_DIR_INFORMATION)pActual)->FileName;break;caseFileBothDirectoryInformation:fileNameLength=((PFILE_BOTH_DIR_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_BOTH_DIR_INFORMATION)pActual)->FileName;break;caseFileNameInformation:fileNameLength=((PFILE_NAMES_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_NAMES_INFORMATION)pActual)->FileName;break;caseFileIdFullDirectoryInformation:fileNameLength=((PFILE_ID_FULL_DIR_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_ID_FULL_DIR_INFORMATION)pActual)->FileName;break;caseFileIdBothDirectoryInformation:fileNameLength=((PFILE_ID_BOTH_DIR_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_ID_BOTH_DIR_INFORMATION)pActual)->FileName;break;caseFileIdExtdDirectoryInformation:fileNameLength=((PFILE_ID_EXTD_DIR_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_ID_EXTD_DIR_INFORMATION)pActual)->FileName;break;caseFileIdGlobalTxDirectoryInformation:fileNameLength=((PFILE_ID_GLOBAL_TX_DIR_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_ID_GLOBAL_TX_DIR_INFORMATION)pActual)->FileName;break;caseFileIdExtdBothDirectoryInformation:fileNameLength=((PFILE_ID_EXTD_BOTH_DIR_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_ID_EXTD_BOTH_DIR_INFORMATION)pActual)->FileName;break;case79://FileId64ExtdBothDirectoryInformation (win11 23H2以后)fileNameLength=((PFILE_ID_64_EXTD_BOTH_DIR_INFORMATION)pActual)->FileNameLength;pFileName=((PFILE_ID_64_EXTD_BOTH_DIR_INFORMATION)pActual)->FileName;break;default:gotoFINAL;}if(fileNameLength<=0||pFileName==NULL)gotoFINAL;FINAL:returnisRet;}这里对目录/文件信息处理的时候,需要根据类型转换相应的结构,然后从数据结构中找到对应的目录名/文件名。上述对目录名/文件名的获取应该覆盖了目前大部分的系统,起码我测试的win7、win8、win10、win11以及同样内核版本的server系统的x64版本是都没有问题的。
OK,目前文件隐藏的所有逻辑就已经结束了,如果还有不懂MiniFilter框架的,后边我可能会出一个MiniFilter的教程,或者大家可以去其他的地方先学习一下这个框架。
