导语:作者详细介绍了如何对Gray Energy恶意软件的一个样本进行脱壳。
最近我偶然发现了一个恶意软件样本,它是针对乌克兰能源基础设施攻击行动中Gray Energy恶意软件的一部分。我在virutotal 上运行了该文件,许多反病毒软件标记为Gray Energy。我试图在互联网上搜寻更多相关研究,但没有找到。因为没有帖子分析这个样本,所以我决定写一个。
在本文中你会学到以下内容:
1.如何调试Windows服务应用程序DLL
2.了解如何使用EBFE调试技术
3.DLL二进制文件脱壳
4.如何转储内存中脱壳的可执行文件
一、识别恶意软件
使用一些基本的静态分析工具,可以知道它是一个64位Windows DLL。
因为它是一个DLL,我们需要看到导出表。导出函数只有一个ServiceMain。这种方法通常由Windows服务应用程序DL导出。这是在向Windows服务应用程序发出请求时调用的函数。
检查导入部分,可以看到导入了以下DLL。但是当我们进一步的分析时,发现并非所有导入的DLL都在使用。
二、Windows服务应用程序简介
如果已经了解Windows Service,那么建议跳过本节。我将从恶意软件作者的角度描述有关Windows服务的所有必要内容,但如果有兴趣了解它,那么可以参考本文末尾参考部分中的链接。
什么是Windows服务?
Windows服务应用程序用于创建长时间运行的后台应用程序,可以在系统引导/重新引导时自动启动它并且没有用户界面。可以将服务置于各种状态,如启动、停止、暂停、恢复和重新启动,所有这些都由Windows服务控制器(services.exe)管理。这些功能使其成为恶意软件的理想选择,可以在后台运行任何恶意软件,并且在重启时也可以长时间运行。服务的实际用例类似于Web服务器service,记录机器性能指标,如CPU、RAM等。服务可执行文件是具有已定义入口点的DLL。
可以编写一个服务作为独立进程或作为服务控制管理器(svchost.exe)进程的一部分运行(它为每个服务创建一个线程,并允许该服务创建更多线程)。如果服务在SC中运行,则SC为服务创建线程,然后加载其DLL,并调用服务入口点以将服务移动到其状态(首先启动,然后最终停止)。从svchost.exe进程(系统服务)创建线程,可为其提供危险的系统权限,但可以在用户帐户的特定安全上下文中运行DLL,该安全上下文因登录用户而异。
服务应用程序需要以下项:
要创建服务DLL,需要满足特定要求,如下所示:
1.Main Entry point: 这是通过调用StartServiceCtrlDispatcher注册服务所必需的,是DLL入口点。
2.Service Entry point: 这是DLL导出项中的ServiceMain,此功能的任务如下:
· 初始化从DLL入口点延迟的任何必要项。注册服务控制处理程序,它将处理服务停止、暂停、继续、关闭等控制命令。
· 将服务状态设置为SERVICE_PENDING,然后设置为SERVICE_RUNNING。在出错和退出时将状态设置为SERVICE_STOPPED。
· 执行启动任务。就像创建线程/事件/互斥/ IPC /等一样。
3.Service Control Handler: 服务控制处理程序已在ServiceMain入口点中注册。每个服务都必须有一个处理程序来处理来自SCM的控制请求。此处理程序将在SCM的上下文中调用,并将保持SCM,直到它从处理程序返回。Service Handler在各种事件上调用,如start、stop、paused等,它们作为参数传递给处理函数。
三、基本静态分析
我们首先对Dll入口点进行静态分析,这是第一个甚至可能在ServiceMain之前执行的函数。下面是Dll入口点的反汇编。
进一步查看反汇编,发现有一些内存分配和内存操作。还有一个有趣的函数是在地址0x2c0202bc处找到的,这个函数是在分配内存之后调用的,它似乎像解密函数,或者至少准备好解密。以下是此函数的反汇编。
由于存在一些XOR运算,其值从寄存器rsp + 0x68中选取,之后将某些操作数据写入[rbx + rsi * 2]转换为相同的地址。我们可以在动态分析中验证这一点。由于IDA和radare2分析都没有识别出很多函数,所以我确信可执行文件被加壳。
让我们看一下ServiceMain的反汇编代码。
这些指示似乎没有任何意义。这进一步证实了我们对可执行文件加壳的怀疑。我们可以使用radare2熵计算函数来检查执行中每个段的熵。如果存在任何具有高熵的段,则意味着该段保存加密数据。我们可以使用radare2 iS entropy命令来计算每个段的熵,下面是该命令的结果。
可以清楚地看到.text段与其他段相比具有非常高的熵。这证实了我们对可执行文件加壳的怀疑。在接下来章节中,我们将尝试为服务应用程序设置调试环境,因为它不像其他Windows应用程序那样直接,并使用动态分析提取加壳的可执行文件。
四、如何调试服务应用程序DLL
如果这是一个普通的DLL,我们可以使用Immunity调试器来进行调试,但服务应用程序DLL是不同的,因为它们必须自己注册并在执行的最初几秒内声明其状态,在运行主入口点之前服务控制管理器(SCM)应该知道服务将要运行,并且DLL仅在SCM的上下文中运行。
因此,挑战在于我们无法通过ad调试器获取DLL入口点。如果可以设法在SCM运行DLL时暂停执行DLL入口点,就能克服此限制。
在做了一些研究后,我看到了一种名为EBFE的技术,可以在此链接(link)上阅读更多相关信息。在该技术中,我们在要插入断点处插入一个无限循环,一旦线程执行此指令,它就会将其置于无限循环中。 EBFE是一个指向自身的跳转指令代码,这将使执行线程处于无限循环中,然后我们一直将调试器连接到进程并开始调试。
接下来的问题是,如果我们将调试器附加到SCM,我们如何知道SCM生成的子进程?实际上非常简单,一旦CPU执行无限循环指令,CPU消耗值将上升到非常高的值,例如90-100%。我们可以使用一个系统内部工具process explorer来获取进程的ID号。
正如在上图中看到的,一旦CPU执行EBFE指令,它就会进入无限循环,这会将CPU消耗增加到95-100%,这表明我们的进程已准备好连接。
现在我们已经弄清楚如何将调试器附加到_Service Application _,接下来我们必须将此指令放在将被执行的点上,即DLL的入口点。有两个点,可以放置EBFE,即ServiceMain(服务入口点)和DllEntry(DLL入口点)。我们将EBFE指令放在这两个函数上。在替换双字节指令之前,必须记下要替换的原始两个字节。一旦命中无限循环,我们将用原始字节替换它并继续调试。
五、动态脱壳
让我们从分析Dll入口点开始,因为在这两个函数中只有这个函数有一些合理的代码。
首先,内存是根据原始可执行文件的大小分配的,它分配内存的方式很奇怪,它指定了想要分配的内存块的基地址,如果失败那么它以10000h的间隔值从100000h处开始试图分配内存。我们将不得不关闭此地址,因为脱壳的可执行文件位于此地址。
然后它更改分配的内存的内存权限,并将每个段(.text,.rdata)复制到新分配的内存。
然后它使用去壳代码中的Dll入口点函数修补当前DLL入口点。在修补内存地址之前,它会将内存权限更改为写入,然后将其还原为“读取”和“执行”。
然后它循环脱壳DLL的导入地址表(IAT),并加载IAT中存在的DLL并解析导入函数并将其添加到表中。
这是代码脱壳阶段,之后解码IAT,然后代码跳转到原始的Dll入口点以便执行。
六、转储脱壳的代码
我们之前在脱壳可执行文件的地址中记录的内存地址,如下面的转储中所示。
我们将使用内置在X64-dbg中的Scylla插件来转储可执行文件。必须指定可执行文件的基地址以及要用于恢复PE的内存大小,可以从地址列旁边的内存面板和调试器的大小(在我们的示例中为23000)中查看并单击dump PE保存可执行文件。
七、脱壳的二进制文件
脱壳后的二进制文件基本信息如下所示:
已脱壳二进制文件的导入表部分。
更多导入部分显示二进制文件使用HTTP与C&C进行通信。
我们可以看到在ServiceMain函数中通过调用RegisterServiceCtrlHandleW和SetServiceStatus注册服务,这意味着我们可以确定它确实是服务应用程序。
八、总结
我们设法对服务应用程序DLL进行脱壳,这个壳是专门设计的DLL,我们观察到二进制文件的脱壳,然后将Dll入口点修补到原始代码。它不是一种特殊的反调试技术,这使得它非常简单,对初学者来说很有用。我们还学习了如何在此过程中转储内存中的二进制文件。
参考
1. Creating Windows Service Application in C++