上一篇介绍了系统服务挂钩并提供了最简单的例子,接下来主要记录我对这种技术应用的研究心得,比较初浅,不对请高手指教!
下面主要以代码为主(未经严格测试,仅供学习参考),实现了
1、 保护文件/目录不被删除
2、 隐藏文件/目录
3、 隐藏进程
4、 保护进程不被结束
5、 保护注册表键不被打开
6、 保护注册表键不被删除
网上有几篇文章介绍了部分功能,并提供的源码。所以我主要把对源码的理解写下来,并对源码做简化,更利于理解。
保护文件/目录不被删除
挂钩 ZwSetInformationFile
NTSTATUS
ZwSetInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
当FileInformationClass= FileDispositionInformation时,FileInformation指向一个
FILE_DISPOSITION_INFORMATION 结构,其定义如下:
typedef struct _FILE_DISPOSITION_INFORMATION {
BOOLEAN DeleteFile;
} FILE_DISPOSITION_INFORMATION;
如果DeleteFile被设为TRUE时,那么当 ZwClose 被调用后文件将被删除。这种情况下我们只要返回STATUS_NO_SUCH_FILE或STATUS_ACCESS_DENIED。
NTSTATUS Hook_ZwSetInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
)
{
NTSTATUS rc ;
// sets the DeleteFile member of a FILE_DISPOSITION_INFORMATION to TRUE,
// so the file can be deleted when ZwClose is called to release the last open
// handle to the file object.
// The caller must have opened the file with the DELETE flag set
// in the DesiredAccess parameter.
if ( FileInformationClass == FileDispositionInformation )
{
// 取文件名称
PVOID Object;
if ( ObReferenceObjectByHandle( FileHandle , 0 , 0 , KernelMode ,
&Object , NULL ) == STATUS_SUCCESS )
{
int BytesReturn ;
UCHAR Name[1024] ;
BOOLEAN *pbDeleteFile = NULL ;
PUNICODE_STRING lpuName = NULL ;
extern NTSTATUS ObQueryNameString(void *, void *, int size, int *);
RtlZeroMemory( Name , sizeof(Name) ) ;
if ( ObQueryNameString( Object , Name , sizeof(Name) ,
&BytesReturn ) == STATUS_SUCCESS )
{
lpuName = (PUNICODE_STRING) Name ;
}
ObDereferenceObject(Object) ;
if ( lpuName->Length > 0 &&
wcscmp( lpuName->Buffer , L”要保护的文件名” ) == 0 )
{
return STATUS_NO_SUCH_FILE ;
}
}
}
rc = gfn_RealZwSetInformationFile( FileHandle , IoStatusBlock ,
FileInformation , Length , FileInformationClass ) ;
return rc ;
}
隐藏文件/目录
挂钩 ZwQueryDirectoryFile
注:该函数未文档化,在ntifs.h内定义,挂钩前先用 extern 声明。
extern NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile(
IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN BOOLEAN bRestartQuery);
参数比较多,看得头都大了^_^,其实真正重要的参数就三个
钩子函数先调用真正的函数,当FileInfoClass= FileBothDirectoryInformation(3)时,
FileInformationBuffer返回请求的目录下的子目录和文件,是一组FILE_BOTH_DIR_
INFORMATION结构:
typedef struct _FILE_BOTH_DIR_INFORMATION {
ULONG NextEntryOffset;
ULONG FileIndex;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaSize;
CCHAR ShortNameLength;
WCHAR ShortName[12];
WCHAR FileName[1];
} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
我们要隐藏某个文件或目录,只需要把该结点从链表中删除。需要注意的是,hEvent
参数或IoApcRoutine参数如果传入有效的值,表示函数以异步的方式处理,这时应该不做其它处理,直接调用真正的函数。
NTSTATUS Hook_ZwQueryDirectoryFile (
IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN BOOLEAN bRestartQuery
)
{
NTSTATUS rc;
// 执行真正的ZwQueryDirectoryFile函数
rc = ((gfn_RealZwQueryDirectoryFile))(
hFile,
hEvent,
IoApcRoutine,
IoApcContext,
pIoStatusBlock,
FileInformationBuffer,
FileInformationBufferLength,
FileInfoClass,
bReturnOnlyOneEntry,
PathMask,
bRestartQuery);
if ( NT_SUCCESS(rc) &&
FileInfoClass == FileBothDirectoryInformation &&
hEvent == NULL &&
IoApcRoutine == NULL )
{
PVOID Object;
BOOLEAN bLastOne ;
PFILE_BOTH_DIR_INFORMATION pLastFileInfo ;
PFILE_BOTH_DIR_INFORMATION pFileInfo ;
WCHAR wszPath[1024] = L"" ;
// 取父目录路径
if ( ObReferenceObjectByHandle( hFile , 0 , 0 , KernelMode ,
&Object , NULL ) == STATUS_SUCCESS )
{
// 遍历链表,重点部分!
pLastFileInfo = NULL;
pFileInfo = (PFILE_BOTH_DIR_INFORMATION)FileInformationBuffer ;
do
{
bLastOne = !( pFileInfo->NextEntryOffset );
if ( pFileInfo->FileName )
{
DbgPrint( "[hooksys] find file&directory = %S " , pFileInfo->FileName ) ;
if ( wcsstr( pFileInfo->FileName , L"hidefile" ) != NULL )
{
if( bLastOne )
{
if(pFileInfo ==
(PFILE_BOTH_DIR_INFORMATION)
FileInformationBuffer )
{
rc = STATUS_NO_SUCH_FILE ;
}
else
{
pLastFileInfo->NextEntryOffset = 0;
}
break;
}
else
{
int iPos = ((ULONG)pFileInfo) –
(ULONG)FileInformationBuffer;
int iLeft = (ULONG)FileInformationBufferLength - iPos –
pFileInfo->NextEntryOffset;
RtlCopyMemory( (PVOID)pFileInfo, (PVOID)( (char *)pFileInfo
+ pFileInfo->NextEntryOffset ), (ULONG)iLeft );
continue;
}
}
}
// 移到下一个结点
pLastFileInfo = pFileInfo;
pFileInfo = (PFILE_BOTH_DIR_INFORMATION)( (ULONG)pFileInfo
+ pFileInfo->NextEntryOffset );
}while(!bLastOne);
}
return rc ; }
pFileInfo->FileName 只提供文件名或目录名,如果要取得完整路径,需要通过hFile取,如同上面的例子一样。我使用比较简单的算法,只要文件/目录名中含有”hidefile”,就让它消失!其实这个只能在explorer中隐藏,在命令提示符下仍然可以cd进去,为了更彻底点,
我拦截了ZwOpenFile
NTSTATUS Hook_ZwOpenFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG ShareAccess,
IN ULONG OpenOptions
)
{
NTSTATUS rc;
if ( ObjectAttributes->ObjectName )
{
DbgPrint( "[hooksys] ZwOpenFile = %S " ,
ObjectAttributes->ObjectName->Buffer ) ;
if ( wcsstr( ObjectAttributes->ObjectName->Buffer , L"hidefile" ) != 0 )
return STATUS_NO_SUCH_FILE ;
}
rc = gfn_RealZwOpenFile( FileHandle , DesiredAccess ,
ObjectAttributes , IoStatusBlock , ShareAccess , OpenOptions ) ;
return rc ;
}
下面主要以代码为主(未经严格测试,仅供学习参考),实现了
1、 保护文件/目录不被删除
2、 隐藏文件/目录
3、 隐藏进程
4、 保护进程不被结束
5、 保护注册表键不被打开
6、 保护注册表键不被删除
网上有几篇文章介绍了部分功能,并提供的源码。所以我主要把对源码的理解写下来,并对源码做简化,更利于理解。
保护文件/目录不被删除
挂钩 ZwSetInformationFile
NTSTATUS
ZwSetInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
当FileInformationClass= FileDispositionInformation时,FileInformation指向一个
FILE_DISPOSITION_INFORMATION 结构,其定义如下:
typedef struct _FILE_DISPOSITION_INFORMATION {
BOOLEAN DeleteFile;
} FILE_DISPOSITION_INFORMATION;
如果DeleteFile被设为TRUE时,那么当 ZwClose 被调用后文件将被删除。这种情况下我们只要返回STATUS_NO_SUCH_FILE或STATUS_ACCESS_DENIED。
NTSTATUS Hook_ZwSetInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
)
{
NTSTATUS rc ;
// sets the DeleteFile member of a FILE_DISPOSITION_INFORMATION to TRUE,
// so the file can be deleted when ZwClose is called to release the last open
// handle to the file object.
// The caller must have opened the file with the DELETE flag set
// in the DesiredAccess parameter.
if ( FileInformationClass == FileDispositionInformation )
{
// 取文件名称
PVOID Object;
if ( ObReferenceObjectByHandle( FileHandle , 0 , 0 , KernelMode ,
&Object , NULL ) == STATUS_SUCCESS )
{
int BytesReturn ;
UCHAR Name[1024] ;
BOOLEAN *pbDeleteFile = NULL ;
PUNICODE_STRING lpuName = NULL ;
extern NTSTATUS ObQueryNameString(void *, void *, int size, int *);
RtlZeroMemory( Name , sizeof(Name) ) ;
if ( ObQueryNameString( Object , Name , sizeof(Name) ,
&BytesReturn ) == STATUS_SUCCESS )
{
lpuName = (PUNICODE_STRING) Name ;
}
ObDereferenceObject(Object) ;
if ( lpuName->Length > 0 &&
wcscmp( lpuName->Buffer , L”要保护的文件名” ) == 0 )
{
return STATUS_NO_SUCH_FILE ;
}
}
}
rc = gfn_RealZwSetInformationFile( FileHandle , IoStatusBlock ,
FileInformation , Length , FileInformationClass ) ;
return rc ;
}
隐藏文件/目录
挂钩 ZwQueryDirectoryFile
注:该函数未文档化,在ntifs.h内定义,挂钩前先用 extern 声明。
extern NTSYSAPI NTSTATUS NTAPI ZwQueryDirectoryFile(
IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN BOOLEAN bRestartQuery);
参数比较多,看得头都大了^_^,其实真正重要的参数就三个
钩子函数先调用真正的函数,当FileInfoClass= FileBothDirectoryInformation(3)时,
FileInformationBuffer返回请求的目录下的子目录和文件,是一组FILE_BOTH_DIR_
INFORMATION结构:
typedef struct _FILE_BOTH_DIR_INFORMATION {
ULONG NextEntryOffset;
ULONG FileIndex;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaSize;
CCHAR ShortNameLength;
WCHAR ShortName[12];
WCHAR FileName[1];
} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;
我们要隐藏某个文件或目录,只需要把该结点从链表中删除。需要注意的是,hEvent
参数或IoApcRoutine参数如果传入有效的值,表示函数以异步的方式处理,这时应该不做其它处理,直接调用真正的函数。
NTSTATUS Hook_ZwQueryDirectoryFile (
IN HANDLE hFile,
IN HANDLE hEvent OPTIONAL,
IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
IN PVOID IoApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK pIoStatusBlock,
OUT PVOID FileInformationBuffer,
IN ULONG FileInformationBufferLength,
IN FILE_INFORMATION_CLASS FileInfoClass,
IN BOOLEAN bReturnOnlyOneEntry,
IN PUNICODE_STRING PathMask OPTIONAL,
IN BOOLEAN bRestartQuery
)
{
NTSTATUS rc;
// 执行真正的ZwQueryDirectoryFile函数
rc = ((gfn_RealZwQueryDirectoryFile))(
hFile,
hEvent,
IoApcRoutine,
IoApcContext,
pIoStatusBlock,
FileInformationBuffer,
FileInformationBufferLength,
FileInfoClass,
bReturnOnlyOneEntry,
PathMask,
bRestartQuery);
if ( NT_SUCCESS(rc) &&
FileInfoClass == FileBothDirectoryInformation &&
hEvent == NULL &&
IoApcRoutine == NULL )
{
PVOID Object;
BOOLEAN bLastOne ;
PFILE_BOTH_DIR_INFORMATION pLastFileInfo ;
PFILE_BOTH_DIR_INFORMATION pFileInfo ;
WCHAR wszPath[1024] = L"" ;
// 取父目录路径
if ( ObReferenceObjectByHandle( hFile , 0 , 0 , KernelMode ,
&Object , NULL ) == STATUS_SUCCESS )
{
// 遍历链表,重点部分!
pLastFileInfo = NULL;
pFileInfo = (PFILE_BOTH_DIR_INFORMATION)FileInformationBuffer ;
do
{
bLastOne = !( pFileInfo->NextEntryOffset );
if ( pFileInfo->FileName )
{
DbgPrint( "[hooksys] find file&directory = %S " , pFileInfo->FileName ) ;
if ( wcsstr( pFileInfo->FileName , L"hidefile" ) != NULL )
{
if( bLastOne )
{
if(pFileInfo ==
(PFILE_BOTH_DIR_INFORMATION)
FileInformationBuffer )
{
rc = STATUS_NO_SUCH_FILE ;
}
else
{
pLastFileInfo->NextEntryOffset = 0;
}
break;
}
else
{
int iPos = ((ULONG)pFileInfo) –
(ULONG)FileInformationBuffer;
int iLeft = (ULONG)FileInformationBufferLength - iPos –
pFileInfo->NextEntryOffset;
RtlCopyMemory( (PVOID)pFileInfo, (PVOID)( (char *)pFileInfo
+ pFileInfo->NextEntryOffset ), (ULONG)iLeft );
continue;
}
}
}
// 移到下一个结点
pLastFileInfo = pFileInfo;
pFileInfo = (PFILE_BOTH_DIR_INFORMATION)( (ULONG)pFileInfo
+ pFileInfo->NextEntryOffset );
}while(!bLastOne);
}
return rc ; }
pFileInfo->FileName 只提供文件名或目录名,如果要取得完整路径,需要通过hFile取,如同上面的例子一样。我使用比较简单的算法,只要文件/目录名中含有”hidefile”,就让它消失!其实这个只能在explorer中隐藏,在命令提示符下仍然可以cd进去,为了更彻底点,
我拦截了ZwOpenFile
NTSTATUS Hook_ZwOpenFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG ShareAccess,
IN ULONG OpenOptions
)
{
NTSTATUS rc;
if ( ObjectAttributes->ObjectName )
{
DbgPrint( "[hooksys] ZwOpenFile = %S " ,
ObjectAttributes->ObjectName->Buffer ) ;
if ( wcsstr( ObjectAttributes->ObjectName->Buffer , L"hidefile" ) != 0 )
return STATUS_NO_SUCH_FILE ;
}
rc = gfn_RealZwOpenFile( FileHandle , DesiredAccess ,
ObjectAttributes , IoStatusBlock , ShareAccess , OpenOptions ) ;
return rc ;
}
由于代码都大同小异,后面几项功能的代码不一一帖出了。
隐藏进程挂钩 ZwQuerySystemInformation ,未文档化的函数,函数定义参考:
NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation
(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
NTSTATUS
NTAPI
ZwQuerySystemInformation
(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
可以在 winternl.h 中找到
SYSTEM_INFORMATION_CLASS 的定义
typedef enum _SYSTEM_INFORMATION_CLASS {
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;
当 SystemInformationClass== SystemProcessInformation时,
SystemInformation指向一个SYSTEM_PROCESS_INFORMATION 结构数组,其中每个成员表示系统中运行的每个进程。注:MSDN上如是说,但网上的代码却是说指向
SYSTEM_PROCESSES
struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters;
struct _SYSTEM_THREADS Threads[1];
};
两个结构大小差距甚大,意义也不尽相同。
测试后发现
SYSTEM_PROCESSES可以正常工作。
保护进程不被结束可以挂钩
ZwTerminateProcess ,