基于Bochs虚拟机实现恶意软件脱壳
原文链接: https://www.fireeye.com/blog/threat-research/2020/04/code-grafting-to-unpack-malware-in-emulation.html
翻译: 看雪翻译小组-Nxe
校对: 看雪翻译小组-八企鹅
这篇博客将继续FLARE脚本系列, 讨论如何修补IDA Pro数据库文件(IDB)以交互式仿真代码的问题. 虽然对恶意软件分析或脱壳的最快方法往往是运行恶意软件, 但恶意软件并不总是在虚拟机中成功执行. 我在IDB模式下使用IDA Pro的Bochs集成来绕过繁琐的调试场景, 来快速获得结果. Bochs可以在没有操作系统的情况下,直接从IDB中模拟bochs VM中的操作码。
Bochs的IDB模式消除了切换VM, 调试器设置、消除反分析手段以及使程序计数器导向至所需逻辑之类的让你分心的事情. 唉, 如果没有操作系统, 就可以没有加载器或动态导入. 执行仅限于在IDB中找到的操作码. 这排除了模拟调用导入字符串函数或内存分配器的程序. Tom Bennett的flare-emu提供了这些仿真版本, 但对于未经准备的分析(特别是当我不知道是否会有回报时), 我更喜欢交互式地检查寄存器和内存, 以调整我的策略.
如果我可以像flare-emu那样, 把自己的导入函数带到Bochs中去呢? 我已经设计出了这样一种技术, 我把它叫做代码嫁接. 在这篇文章中, 我将讨论如何将常用函数的替代函数静态链接到IDB中, 以便从Bochs中获得更多有用的信息. 我将演示如何在一个EVILNEST样本中使用这个方法从模拟内存中脱壳和转储下一阶段的payloads. 我还将展示我如何将一个棘手的调用序列从一个IDB复制到另一个IDB, 这样我就可以将脱壳过程全部保留在一个Bochs调试会话中.
EVILNEST场景
我的样本(MD5哈希值为37F7F1F691D42DCAD6AE740E6D9CAB63
, 可以在VirusTotal上找到)是一个EVILNEST变体, 它在调用中间payload之前用配置数据填充堆栈. 图1显示了这个不寻常的调用位置.
图1: 中间payload的调用位置
图1中的代码在一个被挖空的 iexplore.exe
进程中的远程线程中执行; 该恶意软件也使用了反分析技术. 在掌握了中间payload阶段后, 我想在不使用反分析的多进程调试情况下去脱壳下一阶段的payloads. 我可以在恶意软件中添加一些函数调用, 从而在Bochs中运行所有相关逻辑. 下面我将展示如何做到这一点.
代码雕刻
我需要将一些常见的函数的操作码注入到我的IDB中并在Bochs中模拟. 我使用简单的C语言实现对所选择函数的构建, 并将其编译成二进制程序. 图2显示了其中一些替代者.
图2: 常用函数的简单实现
我编译了这些函数然后使用类似图3的IDA Python代码来提取这些函数的操作码字节.
图3: 函数提取
我在一个IDA Python脚本中设计了一个函数操作码库, 如图4所示. 图中底部的非标准函数操作码都是尽可能简洁地手动汇编的, 使其大致能返回特定的值, 并按照调用约定来操作(或不操作)堆栈.
图4: 提取的函数操作码
以简单的函数memcpy
为例, 我实现了一个内存分配器. 分配器引用了全局状态数据, 意味着仅将它注入进IDB不能正常工作. 我浏览了反汇编代码来找到对全局操作数的引用, 并将它们模板化以使用Python的format
方法. 图5显示了malloc
的一个例子.
图5: HeapAlloc 模板代码
如图6所示, 我将桩函数按名字编组起来, 一是为了向外调用我需要打补丁的函数, 二是为了方便我在遇到使用实例时添加更多的函数桩. 我为free
指定的重整名也就是别名是operator delete
.
图6: 函数桩和关联名
为了将这些函数注入进二进制程序, 我编写了代码用来寻找给定大小的下一个可用的段地址. 避免占用内存低地址, 因为Bochs将它的加载器段放在了0x10000
地址以下. 在我的代码段的代码旁边, 我为内存分配器使用的数据分配了空间. 图7显示了将这些函数和数据在IDB中打补丁并给每个位置命名的结果(桩函数前缀是stub_
).
图7: 注入进IDB的数据和代码
脚本接下来会遍历二进制程序中的所有相关调用, 并把它们替换为新添加段中的桩函数实现. 如图8所示, IDAPython的Assemble
函数省去了手动计算call
操作数的偏移量的工作. 要注意的是Assemble
函数在这里能正常工作, 但如果是更大的任务, Hex-Rays推荐使用专用的汇编器, 比如Keystone Engine和它的Keypatch用于IDA Pro的插件.
图8: 汇编一个call指令并将一处call修补为导入指令的简略流程
代码嫁接脚本将所有相关的调用都更新为类似图9中的结果, 目标函数被替换为对之前注入的 stub_
实现的调用. 这防止了Bochs在IDB模式下运行到这些调用时出现错误, 因为现在所有调用指令都指向了IDB中的有效代码.
图9: 修补过的运算符new()调用位置
处理EVILNEST
这个释放器(dropper)的调试场景略有不便, 而且它也为payload的入口地址设置了一个很不寻常的调用点. 我使用Bochs来运行释放器, 直到它将配置数据放在了栈中, 之后我使用IDA Python的idc.get_bytes
函数来提取产生的栈数据. 我编写了IDA Python脚本代码来遍历栈数据并将push指令汇编到payload IDB中, 使其指向到导出DLL的调用指令. 这让我能在一个单独的会话中调试来自Bochs的脱壳过程.
点击自己合成的调用的开头位置, 然后点击F4在Bochs中运行它. 我迎来的是图10中的警告, 表示修补过的IDB与调试器的描述不匹配(在Bochs的IDB模式中这么说是不正确的). Bochs 执行了我注入的操作码, 并产生了预期的结果.
图10: 修补警告
我小心地观察着指令指针接近并通过了IsDebuggerPresent
的检查. 因为我注入的桩函数(stub_IsDebuggerPresent
), 它通过了检查, 返回值为0, 如图11所示.
图11: 通过IsDebuggerPresent检查
我让程序计数器前进到0x1A1538
地址, 刚好超过了脱壳的过程. 图12展示了此时的寄存器状态, EAX
的值由我假的堆分配器返回, 正好是我要访问的.
图12: 运行到脱壳程序的末尾并准备查看结果
图13显示了在这个位置确实有IMAGE_DOS_SIGNATURE
(“MZ”). 我使用idc.get_bytes()
在假的堆位置来转储脱壳后的二进制程序, 并将它保存用于分析.
图13: 转储脱壳后的二进制程序
通过Bochs IDB模式, 我还能够使用IDA Pro的交互式调试界面, 来尝试操作运行过程和遍历不同分支, 为这个恶意程序解包另一个payload.
结论
虽然动态分析有时是最快的方法, 但设置环境和找到的细节会分散我的注意力, 所以我开发出了一个可以在Bochs中模仿的例程来绕开这些干扰, 同时还能得到结果. 将代码注入到IDB中, 扩大了我可以使用的函数集, 让我可以从Bochs中得到更多的东西. 这反过来又让我可以做更多的即兴实验, 一次性的字符串解码, 或者在大规模攻击之前验证假设. 这也让我可以对那些无法正确加载的样本进行动态实验, 比如脱壳后的代码中的PE头损坏或不正确的代码.
我已将这个代码嫁接工具作为flare-ida GitHub 仓库的一部分分享出去. 如果要在自己的分析过程中使用该工具:
- 在IDA Pro的IDA Python的命令提示符处, 运行
code_grafter.py
或将其作为一个模块导入. - 实例化一个
CodeGrafter
对象, 并调用其graftCodeToIdb()
方法:CodeGrafter().graftCodeToIdb()
- 使用IDB模式的Bochs来方便的执行你修改过的样本和实验程序!
这篇帖子让大家明白, 我只是为了避免和IDA断了联系才会这么做. 如果你也是喜欢用Bochs和IDA的粉丝, 那么这就是我送给你的礼物. 请享用!
本文来源于Lonely Blog -全球网络安全资讯平台, 转载请注明出处: https://blog.wuhao13.xin/1380.html