本帖最后由 ANY.LNK 于 2024-11-2 01:43 编辑
原文地址:https://i.blackhat.com/us-18/Thu-August-9/us-18-Bulazel-Windows-Offender-Reverse-Engineering-Windows-Defenders-Antivirus-Emulator.pdf
发布于Black Hat 2018,原作者Alexei Bulazel(@0xAlexei)
这是已知的首次对WDAV(也是所有安全软件)的模拟器进行深度逆向。
感谢Tavis的启发
为何选择WDAV?
·它已成为Windows内置的默认解决方案
·“Defender”一名已涵盖多个安全缓解、控制程序,在此我们主要关注WDAV的模拟执行功能,而非ATP,Application Guard,Exploit之类
·基于windowsreport.com/windows-defender-enterprise-antivirus于2018年的数据,8%的Windows 7,Windows 8系统正在运行微软AV解决方案,而超过50%的设备正在运行WDAV
·默认以无沙盒的NT AUTHORITY\SYSTEM高权限运行,对此进行漏洞攻击,可以同时完成初始RCE,权限提升和AV绕过
·对攻击者而言,对其的远程访问意外的简单。
目标:mpengine.dll
月度更新的mpam-fe.exe包含如下内容:
·mpengine.dll:“Microsoft Malware Protection Engine”,提供恶意软件扫描和检测功能(其他AV和系统缓解功能由Defender的其他组件处理)
·MPSigStub.exe:“Microsoft Malware Protection Signature Update Stub”
·mpasbase.vdm
·mpasdlta.vdm
·mpavbase.vdm
·mpavdlta.vdm
之前的对Defender JS 引擎的研究
https://twitter.com/0xAlexei/status/963432394551451649
JS引擎用于分析可能的恶意代码,从二进制中逆向
用于动态实验的自定义Loader / Shell:
AV侦测回调:
安全的代价是性能
相关
目录
1.介绍
2.工具和过程
3.逆向工程
4.总结
介绍:为什么使用模拟执行?(同时解答上一篇译文读者的疑惑——译者注)
传统AV模型扫描文件并寻找匹配已知的签名(哈希、字节串、文件路径等),这很容易绕过。
微软的解决方案:在虚拟模拟执行环境中运行未知的二进制文件,寻找恶意行为或匹配已知签名(这不是一个新的解决方案,它至少存在了15年)
概要:
·寻找未知的可能恶意的二进制
·从入口点开始执行,直到结束
-时间
-指令数
-API调用数
-内存使用
等等
·收集运行行为的启发式结果,检查内存或写入磁盘的签名,等等
工具和过程
静态分析:
约12M大小的dll
约30000个函数
IDA Pro分析
-使用BinDiff修补分析过程
微软发布PDB
动态分析和加载器
AV特有的挑战:
·受保护的进程(PPL),即使以本地管理员的身份也无法调试
·自我(完整性)检查
·依据需求的扫描
·代码可达性受配置/启发式方法影响
解决方案:
自定义加载器:Tavis的loadlibrary(git.io/fbp0X)(PS:这个工具我也在用——译者注)
Linux PE加载器,导入Windows API的有效实现方法,且只实现了mpengine.dll的基本功能,没有使用更复杂通用的Windows模拟器或Wine替代
mpclient工具提供了主要扫描界面
-我使用mpclient构建了大约三千给LoC附加工具
最终结果:
Demo:使用mpclient扫描
动态分析,代码概要
了解哪些子系统被命中有助于确定扫描或模拟执行的特征
-断点有些过于细化了
模拟器不会输出任何结果除非识别到恶意软件
IDA的灯塔代码处理插件源自Ret2 Systems / RPISEC中Markus Gaasedelen
从mpengine.dll中获取执行路径很麻烦,因为特权进程
逆向工程
开始
启动模拟
·__rsignal函数提供了Defender扫描的入口点,给予其一个数据偏移量,它将返回恶意软件类型
·Defender使用模拟执行分析其无法通过其他低资源耗费的方式进行分析的可执行文件
·模拟执行的结果将被缓存,同一个二进制文件只会被分析一次,即使多次扫描。
[size=15.3333px]
[size=15.3333px]
模拟器初始化
·分配内存
·初始化多个在模拟过程中会用到的对象和子系统
·加载要分析的二进制文件,重定位函数,处理导入,等等;[size=15.3333px]·在内存内初始化虚拟动态链接库
·记录二进制文件行为并进行启发式分析,节段排列,导入数量,等等
[size=15.3333px]
CPU模拟
·支持多种架构(本文重点关注x86 32位)
·技术上采用动态翻译,而非“模拟执行”
·针对特定架构的软件模拟执行可以处理独特或难以翻译的指令
·子系统非常复杂,其本身即可自行执行(不是本研究的重点,也是我了解最少的部分)
- DT_platform_x86_16 = 0n0
- DT_platform_x86_32 = 0n1
- DT_platform_x86_64 = 0n2
- DT_platform_emu_IL = 0n3
- DT_platform_NETRPF = 0n4
- DT_platform_NETEmu = 0n5
- DT_platform_DTlib32 = 0n6
- DT_platform_DTlib64 = 0n7
- DT_platform_VMProtect = 0n8
- DT_platform_ARM = 0n9
- DT_platform_count = 0n10
复制代码
*_2_IL LIFT(Learned Invariant Feature Transform?)(一种实现了特征点检测(detector),主方向估计(orientation estimation),和描述子生成(feature description)的深度机器学习神经网络)
参考文献:https://www.semanticscholar.org/ ... b80d69b8effdc4cc276(2016)(译者添加)
为IL LIFT分配了独立的架构
从操作码中抓取字节,决定类型,然后依据此选择对应的IL并翻译
软件中的IL模拟执行
模拟器可以在软件中执行字节码
执行IL模拟器
我未观察到此IL软件模拟器在我的研究中被调用。猜测:可能在非x86的宿主操作系统中如Windows on ARM中被使用?
IL到x86的即时翻译
IL码可以被翻译成x86并且执行,每次执行一个基础块。
我观察到此IL到x86的即时编译在研究中得到了应用
(参见微软的VB2005文档)
适用于特定架构的esc处理句柄
架构特异函数提供了对特定架构事件和特定指令的模拟执行的处理
x86_common_context::emulate_CPUID
对x86 CPUID指令的架构特异软件模拟
测绘mpengine
问题:
引擎内部高度不透明
-覆盖全局、断点细观
只有输出是恶意软件检测
解决方案:
从恶意软件的角度出发
mpengine.dll拥有可在我们的恶意软件调用特定Windows API时被调用的函数
创建从内部观测AV的二进制库——挂钩并重用存在的函数,向引擎外部的我们共享视野。
还是mpclient
https://github.com/0xAlexei中的LoC
挂钩OutputDebugStringA
挂钩本地在模拟器中调用OutputDebugStringA时会被调用的的函数指针。
使用Defender中存在的函数同函数参数以及虚拟内存互动
用于保存参数的局部变量同Parameters<1>
通过调用mpengine.dll内部的Parameters<1>参数,进而从虚拟堆栈中获得参数
参数就在模拟器的虚拟内存中
GetString调用把模拟器虚拟内存地址(参数)翻译成真正指针mpengine.dll函数
Demo
挂钩OutputDebugStringA
Myapp.exe可以通过上述方法实现同模拟器外部的I/O通信
阻止模拟执行的因素:
·简单或低代码熵
·尝试链接不支持的动态链接库
·调用不受支持的函数
·优化使用复杂指令
·针对过于新的Windows版本
解决方案:
·加入垃圾代码
·减少调用链接
·禁用所有优化
·针对旧版本Windows
(上述为基于我的经验总结出来的AV模拟器的通病。Defender看起来比其他的更灵活更具适应性,但我仍需要调整编译器设置以获得一致的模拟执行二进制文件)
Windows模拟与环境
1.用户模式环境
虚拟文件系统
通过类似于OutputDebugStringA的钩子Dump文件系统内容——只需传递指向任意数据的void指针
·截至2018.2.28的版本,共模拟了1455个文件
-整个FS可以在1-2秒内被Dump
·大部分是虚假的可执行文件
·少量的假配置文件
·各类“山羊”文本文件
·大量空文件
虚拟注册表,其中包含数千个项:
进程:
模拟出了大量的进程,用于使恶意软件认为这是真实的执行环境
其中,myapp.exe为模拟执行的样本,在不同的mpengine环境中样本的PID也会不同
- 0 - [System Process]
- 4 - System
- 356 - smss.exe
- 608 - csrss.exe
- 624 - winlogon.exe
- 676 - services.exe
- 680 - lsass.exe
- 700 - kav.exe
- 704 - avpcc.exe
- 708 - _avpm.exe
- 712 - avp32.exe
- 716 - avp.exe
- 720 - antivirus.exe
- 724 - fsav.exe
- 728 - norton.exe
- 732 - msmpeng.exe
- 736 - msmpsvc.exe
- 740 - mrt.exe
- 744 - outpost.exe
- 856 - svchost.exe
- 1084 - svchost.exe
- 1268 - spoolsv.exe
- 1768 - explorer.exe
- 1796 - iexplore.exe
- 1800 - outlook.exe
- 1804 - msimn.exe
- 1808 - firefox.exe
- 1812 - icq.exe
- 1816 - yahoomessenger.exe
- 1820 - msnmsgr.exe
- 1824 - far.exe
- 1828 - trillian.exe
- 1832 - skype.exe
- 1836 - googletalk.exe
- 1840 - notepad.exe
- 1844 - wmplayer.exe
- 1848 - net.exe
- 1852 - spawned.exe
- 3904 - myapp.exe
复制代码 (还有不少是杀毒软件进程)
2.用户模式代码
Windows API模拟:
两种类型的Windows API函数
·保持在用户模式→保持在模拟器内
·转向syscall→设立原生模拟执行
就像真正的Windows API - DLL那样执行功能
·符号表明其被称为“vdll”
·存在于模拟器的磁盘和内存中——就像真正的Windows那样
·VDLL不存在于mpengine.dll中只能从VDM库中动态加载
反转的VDLL
模拟器内VDLL模拟:
·保持在模拟器内
·代码在动态翻译系统中完成
·部分模拟会使用硬编码回显
用户名为“JohnDoe”
计算机名为“HAL9TH”
其他
硬编码函数
复杂的函数返回硬编码的值或中断模拟
ws2_32.dll:
Winsock.dll库很独特,其内部包含大量的带有“Mp”的字符串和德国网站
3.用户-内核交互
原生模拟
·复杂的函数无法在模拟器内处理,必须使用原生代码模拟
·类用户模式→内核或虚拟机客户机→宿主机转换
·模拟器到原生翻译通过自定义超调用指令实现 - apicall 0x0F 0xFF 0xF0 [随后4字节]
·apicall的多个函数的桩代码包含在VDLL内
由IDA处理器扩展模块提供的apicall反汇编
g_syscalls
- dt mpengine!esyscall_t
- +0x0 proc : Ptr32 void
- +0x4 encrc : Uint4B
复制代码
apicall指令使用触发器调度g_syscalls表中的函数指针,上图为我们在挂钩OutputDebugStringA时修改的表
执行流程:
模拟的VDLL函数:
原生模拟函数
所有原生模拟函数均使用参数pe_vars_t *,大约0.5M大小的结构包含整个模拟会话的上下结构体
缓存的Parameters函数从模拟堆栈中获取参数
返回值、注册状态,CPU tick计数(时间)等,通过操作pe_vars_t的多个函数进行管理
同虚拟内存交互
mmap函数使对模拟内存空间的访问成为可能
交互页面类似Unicorn Engine和其他同类工具类似
围绕这些函数的封装函数令常见的操作变得容易
4.内核之内
Windows内核模拟
Windows内核组件由原生代码模拟
·对象管理器
·进程管理
·文件系统
·注册表
·同步原语
对象管理器
·这是Windows执行的基础部分——提供内核模式资源管理,例如进程、文件、注册表键值、互斥量等
·Defender支持5种类型的对象:文件、线程、日志、互斥量、信号量
·模拟执行过程中的管理系统区块在原生模拟API调用时保持不变
对象管理类型
5种类型的对象存储在调用表中,由PID和句柄追踪
对象通过C++虚拟方法调用,RTTI被用于从ObjectManager::Object选择特定的子类型
对象管理器整合
对象管理器在一个模拟会话中管理持续系统区块
NTDLL_DLL_NtOpenMutantWorker
NTDLL_DLL_NtSetInformationFileWorker
当前进程句柄模拟为0x1234
VFS(虚拟文件系统)
·原生模拟函数被存储于NTDLL下(但可以用多个apicall stub替代多个VDLL实现访问)
·NTDLL_DLL_VFS_*函数调用内部VFS_*函数之前进行管理工作,这会同虚拟文件系统进行实际接触,调用其方法以操作内容
·同文件系统交互的NTDLL Nt*模拟函数在检查/规范化/清理验证(用于安全化)输入后向下调用为VFS_*函数
VFS特定原生模拟
- ObjMgr_ValidateVFSHandle
- VFS_CopyFile
- VFS_DeleteFile
- VFS_DeleteFileByHandle
- VFS_FileExists
- VFS_FindClose
- VFS_FindFirstFile
- VFS_FindNextFile
- VFS_FlushViewOfFile
- VFS_GetAttrib
- VFS_GetHandle
- VFS_GetLength
- VFS_MapViewOfFile
- VFS_MoveFile
- VFS_Open
- VFS_Open
- VFS_Read
- VFS_SetAttrib
- VFS_SetCurrentDir
- VFS_SetLength
- VFS_UnmapViewOfFile
- VFS_Write
复制代码- dt mpengine!pe_vars_t
- ...
- +0x241e0 vfs
- +0x241e4 vfsState
- +0x241e8 vfsNumVFOs
- : Ptr32 VirtualFS
- : Ptr32 VfsRunState
- : Uint4B
- +0x241ec vfsVFOSizeLimit : Uint4B
- +0x241f0 vfsRecurseLimit : Uint4B
- +0x241f4 vfsFlags
- : Uint4B
- ...
复制代码
5.反病毒检测器
Defender内部函数
内部管理和配置函数可通过apicall访问
MpAddToScanQueue:为文件(例如植入的二进制库)扫描排序
MpCreateMemoryAliasing:在模拟器中设置内存别名
MpReportEvent, MpReportEvent{Ex,W}:报告恶意软件行为用于给启发式检测提供信息
Mp{Get,Set}SelectorBase:获取/设置段寄存器(CS, DS, ES等)
MpUfsMetadataOp:文件扫描时获取元数据
NtControlChannel:为AV引擎提供的类IOCTL管理
MpReportEvent:同Defender的启发引擎交换恶意二进制的信息
AV进程
进程类型基于PID分类——AV进程拥有700 PID
- 700 - kav.exe
- 704 - avpcc.exe
- 708 - _avpm.exe
- 712 - avp32.exe
- 716 - avp.exe
- 720 - antivirus.exe
- 724 - fsav.exe
- 728 - norton.exe
- 732 - msmpeng.exe
- 736 - msmpsvc.exe
- 740 - mrt.exe
- 744 - outpost.exe
复制代码
模拟进程的信息存储于VDLL kernel32.dll的数据(data)结构并在列举时展示
当对AV进程使用TerminateProcess会触发MpReportEvent调用
NtControlChannel选项
弱点研究
A.理解P0的脆弱之处
Tavis的apicall欺骗
·构建拥有一个rwx .text字节段的二进制文件,按需实时生成apicall指令
·在恶意软件.text中的apicall指令触发被攻击者控制的原生模拟
Tavis的NtControlChannel BUG
NtControlChannel(0x12,...)
由用户控制的count(已用最大计数检测0x1000修复)
VFS_Write BUG
- VFS_Write(
- unsigned int hFile,
- char * pBuffer,
- unsigned int nBytesToWrite,
- unsigned int nOffset,
- unsigned int * pBytesWritten
- );
复制代码
堆栈OOB r/w的缓冲区扩大至偏移量,但没有为其分配空间。这样之后可在任意偏移量上运行r/w。
B.通过滥用apicall绕过缓解措施
锁定apicall
·is_vdll_page于2017年6月20日被添加至mpengine.dll中的 __call_api_by_crc,用于验证apicall指令是否源自VDLL
新的启发特性添加↑
·无法仅通过之前提到特殊的.text段或其他恶意软件创建的内存触发apicall了
如果apicall不是由VDLL发起,设置启发并拒绝请求,若满足条件,则继续处理
绕过
apicall存在于整个VDLL中,可在内存中定位并直接被攻击者所控制参数的恶意软件调用,绕过is_vdll_page检查
微软回复:
"We did indeed make some changes to make this interface harder to
reach from the code we’re emulating -however, that was never intended to be a trust boundary.
Accessing the internal APIs exposed to the emulation code is not a security vulnerability..."
“我们已做出改进,使我们正在模拟的代码更难访问此接口。但这不会作为我们的信任边界。访问暴露给模拟代码的内部API不是安全问题。”
绕过示例1:
绕过示例2:
apicall滥用demo:
apicall绕过影响:
·标记、操控分析环境或恶意软件启发检测(NtControlChannel, MpReportEvent)
·使用已知的内存泄露漏洞访问攻击面
·似乎很难缓解此类滥用
C.模糊测试
测试模拟的API
·创立一个二进制文件,深入模拟器内部,反复调用挂钩的WinExec函数请求新数据,随后将数据发送至原生模拟的函数
·内存中的缓冲区传至外部钩子函数用于填充参数
·可以在模拟器内部进行模糊测试,但这个对于记录结果来说更容易
输入信号的生成
使用MWR Labs OSXFuzz project(github.com/mwrlabs/OSXFuzz)的OSX syscall模糊测试代码
没有什么特别的,只需向原生模拟器句柄抛出随机值即可
在每次模拟会话开始时重复做种rand(),并将种子保存在日志中
NtWriteFile溢出
·该函数可以正常访问并由ntdll.dll导出
·特殊的apicall可触发VFS_Write
Tavis的输入在向下调用VFS_Write之前被NtWriteFileWorker进行无害化处理
我测试了NtWriteFile
·大约7分钟8000次调用
·测试了Length实际参数
·复现了Tavis的崩溃,通过NtWriteFile更轻易的达到了代码路径
不幸的是,对VFS_WRITE BUG的补丁同样修复了这个
总结
概要:
我们涵盖了:
·工具化和追踪测试
·x86 CPU基础动态翻译
·32位和64位的二进制Windows环境和模拟执行
·一点安全漏洞研究
没有涵盖:
·非x86 CPU动态翻译,如x64 ARM VMProtect等以及脱壳器集成
·16-bit的模拟执行
·线程模式
·.NET分析
同样在mpengine.dll中的:
脱壳机、解析器、脚本引擎、.NET引擎,其他引擎等等
备注:Lua引擎是签名类型的,攻击者无法利用它
帮助和引用:
参见原文
由于此文多次触发论坛自动机敏感词而无法提交并被删除数据,因此可能存在图片错位、混乱以及格式问题,敬请谅解
|