免责声明
锦鲤安全的技术文章仅供参考,此文所提供的信息仅供网络安全人员学习和参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。如有侵权烦请告知,我们会立即删除并致歉。本文所提供的工具仅用于学习,禁止用于其他,请在24小时内删除工具文件!谢谢!
1. 简介
我们的目标是撰写一系列有关恶意软件分析的文章,并解释从简单的恶意软件二进制文件到最复杂的恶意软件,涵盖大量主题,例如解包、API 解析、C2 提取、C2 模拟,当然还有逆向工程。除了一些动态分析之外,也许还可以使用一些反混淆技术。必要时,我将介绍其他主题,例如 COM(组件对象模型)、密码学、IDC/IDA Python 以及帮助读者更好地理解分析所需的一切内容。
在本系列文章中,我将使用几种工具,并尝试指出可以在哪里使用它们来使事情变得更简单。
我们将分析不同的示例,每个示例都有不同的难度级别,并讨论一些代码行。将一篇文章分成不同的部分,以避免阅读变得如此疲惫。
在 bilibili 可看本章节完整视频:
https://www.bilibili.com/video/BV1kDkNYaEbc/?spm_id_from=333.999.0.0&vd_source=6c9924e717cda25d98f1fbb213e223ad
本篇文章为万字长文,建议收藏。
1.简介
2.必备工具
3.恶意软件分析目标
4.初步信息收集
5.解包概念复习
6.代码注入审查
7.脱壳方法
8.脱壳提取二进制文件
9.逆向分析恶意样本
备注:前面部分基础知识翻译自国外作者亚历山大
2. 必备工具
分析工具:IDA,X64DBG
工具插件:
ScyllaHide:它是一个高级反调试库,可挂钩多个函数以隐藏恶意软件的调试活动:https://github.com/x64dbg/ScyllaHide/releases
DbgChild:它提供对由调试进程创建的子进程的自动检测,并在主进程派生新进程时自动附加 x64dbg 的新实例,从而在很多机会中节省您的时间。 DbgChild 可从以下位置获取:https://github.com/David-Reguera-Garcia-Dreg/DbgChild
Labelless:这是一个非常值得推荐的插件,为逆向者提供了两个关键功能:
x64dbg 和 IDA Pro 之间的标签、函数名、全局变量和注释同步。
为调试进程动态转储内存中的区域,这将很有用,例如,在任务 API 解析和/或字符串解码后转储二进制文件。
Labelless 插件可以从以下地址下载:https://github.com/a1ext/labeless/releases
所有剩余的工具将在我们的分析和未来的许多文章中展示。
2. 恶意软件分析目标
我们在分析二进制文件时要寻找什么?
3.1 内存分析
这是一种极其强大的技术,在过去 10 年中已证明其无限的价值,并在调查过程中用作了解恶意软件感染事件、其后果、副作用的首选方法,并使获取大量证据成为可能可能很难从磁盘或任何其他来源收集。
3.2 网络分析
它是一个非常有用的资源(例如 pcap 文件),可以通过流量分析来理解和检测未经授权的通信(C2 - 命令和控制通道)并进行工件收集(例如二进制文件、恶意文档和 Cobalt beacon)。
3.3 文件系统/磁盘分析
任何调查的最后一个领域,我们可以分析和检测对手入侵、破坏、欺诈、泄漏,当然还有恶意软件感染的副作用。
通过恶意软件分析,有机会从邪恶的来源了解对手的意图和目标,而不仅仅是其影响。换句话说,你可以学习技术、技巧、规避策略,如果幸运的话,你可以收集重要的文件来做出正确的归因(大多数时候,这是一项艰巨的任务)。
3.4 关键分析
1、二进制打包了吗?如果是,那么恶意软件使用的是众所周知的加壳器还是自定义的加壳器?
2、恶意软件使用的网络通信技术/API 集是什么?诸如 Winsock2、Wininet、COM(组件对象模型)或较低版本的技术,如 WSK(Winsock 内核)之类的级别,甚至是自定义实现的技术,正在使用哪种技术?
3、是否使用了任何代码注入或挂钩技术?哪一个?
4、使用哪些反取证技术?有没有反调试技术?防拆?反虚拟机/沙箱?
5、有 API/DLL 编码吗?
6、字符串是否加密?
7、恶意软件使用哪些同步基元?有时它们隐藏重要的反调试技术。
8、恶意软件使用哪些加密算法?
9、威胁使用哪些持久性方法:注册表、服务、任务或内核驱动程序?
10、是否有任何 shellcode 被注入到操作系统进程中?
11、恶意软件是否安装了文件系统微型过滤器驱动程序?
12、如果安装了内核驱动程序,是否安装了任何回调(一种现代挂钩)或计时器?
3.5 本篇文章目标
1、解压恶意软件
2、提取并解密其 C2 的配置数据
4. 初步信息收集
本章分析的样本 hash 如下:
sha256:8ff43b6ddf6243bd5ee073f9987920fa223809f589d151d7e438fd8cc08ce292
使用快速分析工具,主要是内嵌了沙箱api,我们这里用的是免费的 Malware Bazaar
#图一#
根据图一中信息有以下几个重要信息:
1、目标恶意软件似乎来自 Hancitor 家族。
2、它使用 EnumerateProcesses() 函数,因此了解是否有任何特殊原因(例如代码注入);
3、WriteProcessMemory() 的触发方式与我们通常在解包过程和代码注入中看到的那样,因此这里没有消息是个好消息。
当我们再去了解这个家族基本能确定就是 c2 类型远控木马了
5. 解包概念复习
每次我听到有人谈论拆包时,印象似乎都会转化为相同的结论:这可能不是那么容易。当然,正如我们之前提到的,解包样本可能是可能的字符串解密和 API/DLL 解析之前的第一步,例如,但我们需要从某个地方开始,并有一个目标。
5.1 打包程序的原因
1、它使恶意代码对 AV “隐藏”。当然,它并不那么隐蔽,但它是一种软规避技术,让分析师的生活变得有点困难,最终会给防御带来一些问题。
2、打包样本并未揭示实际恶意软件的实际目的。
3、由于需要规避许多反分析技术(反调试器和反 vm 技巧),因此可能很难动态解压缩它。
4、恶意软件通常使用自定义例程将有价值的代码打包到多个层中。
5、最终,整个恶意软件或仅解压缩代码可能是多态的。
5.2 加壳特征
1、它们已用于64 位二进制文件。
2、IAT (导入地址表) 可能已被删除,或者最多只能有一个导入的函数。
3、大多数字符串都是加密的。
4、内存完整性经过检查和保护,因此无法从内存中转储干净的可执行文件,因为原始指令没有在那里完全解码。
5、指令被虚拟化(混淆,装逼说是虚拟化,就是替换指令干等价的事,比如1+1=2,切换成1+5+1-5=2),令人惊讶的是,它被翻译成 RISC 指令(系统指令)。
6、这些虚拟化指令在内存中加密。
7、混淆是基于堆栈的,因此使用静态方法处理虚拟化代码非常困难。
8、大多数虚拟化代码都是多态的,因此有许多虚拟指令引用相同的原始指令。
9、有数千行虚假的 “push” 指令,当然,其中许多都包含死代码和无用的代码。
10、这些保护程序使用无条件跳转实现代码重新排序。
11、所有这些现代加壳程序都使用代码扁平化、许多反调试和反 vm 技术。
12、并非所有 x64 指令都是虚拟化的,因此您会发现一个二进制代码,其中包含虚拟化和非虚拟化(本机)指令的混合。
13、大多数时候,函数的序言和尾声不是虚拟化的。
14、原始代码部分可以“拆分” 和/或分散在程序中,因此指令和数据会混合在一起。
15、引用导入函数的指令可能会归零,甚至替换为 NOP,因此在此"引用" 将被动态恢复。有时这些相同的引用不是归零,但替换为使用 RVA 到同一导入地址的跳转指令,也称为“IAT 混淆”。
16、在 shellcode 和常见恶意软件中使用时,API 名称经过哈希处理。
17、从 native register 到 virtualized register 的转换通常是一对一的,但并非总是如此。此外,还有一个上下文切换组件负责将寄存器和标志信息传输到虚拟机上下文中。
18、虚拟机处理程序来自数据块。
19、许多本机 API 被重定向到转发调用的存根代码。
20、使用混淆技术,例如常量展开、基于模式的混淆、控制间接、内联函数、代码复制和主要不透明的谓词。
5.3 解包前的观察与思考
1、恶意软件真的打包了吗?
2、拥有打包代码的证据是什么?
3、恶意软件是执行自我注入还是远程注入?
4、恶意软件是否执行自我覆盖?
5、payload 写入何处?
6、有效负载将如何执行?
7、在解包过程之后,有什么证据证明有解压的代码?
8、是否有其他打包层?
5.4 打包特点
以下是当我们确定程序是恶意的时候用来评判是否被打包(加壳)的特点:
1、二进制示例具有很少的导入 DLL 和函数。
2、有许多混淆字符串。
3、存在特定的系统调用。
4、非标准部分名称。
5、非常见的可执行二进制部分(只有 .text/.code 部分应该是可执行的)
6、意外的可写部分。
7、高熵部分(通常高于 7.0,但并非总是 - 这是一个弱指标)。
8、部分的原始大小和虚拟大小之间存在很大差异。
9、零大小的部分。
10、缺少与网络通信相关的 API。
11、缺乏恶意软件功能的基本 API(例如,勒索软件中的 Crypt* 功能)。
12、不寻常的文件格式和标头。
13、指向 .text/.code 节以外的其他部分的入口点。
14、资源部分(.rsrc 部分)的大小很大,在代码中后跟 LoadResource( ) 函数。
15、存在叠加层。
16、在 IDA Pro 上打开它,并在彩色条上观察大量数据或未探索的代码。
要强调的是不能以一个特点来确定恶意程序就是打包了,要根据多个条件来对应才能更加确定,比如以下特点不一定准确:
1、大多数示例使用 LoadLibrary() 后跟 GetProcAddress() 动态解析其 API(反射代码注入情况除外)。
2、网络 API 也可以动态解析。
3、格式错误的标头在第一次分析时可能有点难以检测到。
4、Big resource 部分可能不相关,因为它可能仅包含 GUI 构件和数字证书。
5、可能混合了加密/混淆的字符串和纯文本字符串,因此很难确定二进制文件是否打包。
5.5 反调试与反虚拟机
1、反调试技术(时间检查、CPUID、堆检查、调试标志检查、NtSetInformationThread() 等),因此建议使用反调试器插件,例如 x64dbg/x32dbg 上的 ScyllaHide (https://github.com/x64dbg/ScyllaHide)
2、检查 VMware、VirtualBox、Hyper-V 和 Qemu 工件的反 VM 技巧。
3、文件名、主机名和帐户检查(避免使用哈希值作为文件名)。
4、虚拟机上的可用磁盘大小(建议至少 100 GB)
5、测试虚拟机上的处理器数量(两个或更多是合适的)
6、正常运行时间(尝试保持虚拟机快照的正常运行时间超过 20 分钟)。
7、许多无意义的调用(结果不再使用)和不存在的 API(伪 API)。
8、用作反调试技术的异常处理程序。
9、清除软件断点并操纵寄存器 (DR#)(防断点技术)
10、使用典型算法(例如 crc32、conti add_ror13,...)的哈希函数。
11、对 Process Hacker、Process Explorer、Process 等知名工具进行恶意代码检查Monitor 等(建议在使用这些可执行二进制文件之前重命名它们)。
在某些情况,可以尝试不同调试器(如 WinDbg)来分析一些恶意软件威胁,这些威胁仅针对环 3 调试器,而不是内核调试器(最近的案例是 GuLoader 恶意软件)。
5.6 恶意二进制文件修复难题
1、DOS/PE 标头可能已在内存中被销毁或被压缩库修改。
2、在许多情况下,当您从内存中提取二进制文件时,您需要清理它,因为在其 DOS 头(MZ 签名)和 PE 头之前有一些垃圾。
3、入口点 (EP) 可能已归零或错误。
4、解压缩的二进制文件可能会因为已被转储而销毁其 Import 表,并且其地址引用虚拟地址(映射版本而不是未映射版本),因此显示未对齐的部分或无部分。
5、基址错误。
6、PE 格式的字段存在一些不一致之处。
7、可能很难确定 OEP (Original Entry Point) ,它通常出现在使用间接调用(例如调用 [eax] 或 jmp [eax])从解包程序代码转换后出现。此外,存在未解析的 API 可能证明恶意代码没有解析到达 OEP。按时:OEP 是可执行文件在打包之前的入口点(EP)。打包后,新的 EP 将与 打包程序 本身相关联。
8、互斥锁被用作两个解包层之间的一种“解锁密钥”。在这种情况下,如果没有第一阶段发生,则解包的第二阶段不会发生,如果发生了,则确认互斥锁的存在。
9、代码可能正在执行自我覆盖。
10、解压代码的第一阶段不从任何目录运行,而只从特定目录运行。
11、您可以提取诱饵二进制文件。在许多实际情况下,恶意软件作者将一个或多个无用的可执行文件打包为诱饵,以消耗分析师的时间。因此,明智的做法是不要在第一次尝试时相信你已经从内存中解压缩了正确的二进制文件。
这个问题列表非常有限,并且对解压的二进制文件有无穷无尽的其他可能的副作用。当然,对于这些提出的问题中的每一个,都存在不同的解决方案,它们将在本系列的下一篇文章中进行解释并给出示例。无论如何,处理某些问题的几种方法是:
1、从另一个可执行文件(或从自己的恶意软件样本)复制一个好的 PE 标头,并对齐部分,考虑解压缩的二进制文件是未映射的(.text 部分通常以 0x400 开头)还是映射的(.text 部分通常以 0x1000 开头)。
2、通过修复其各自的原始地址来对齐解包的二进制文件(映射地址)的各个部分和 Raw size (原始大小)。此操作通常会修复导入表,并可以可视化导入的功能没有任何问题。注意可能的“陷阱”:一些脱壳的二进制文件不会显示其 Import Table (导入表),直到您对齐了它们的部分。但是,其他恶意软件威胁则不会,即使在解压缩二进制文件后,在 Import Table 中也不显示条目但具有很多功能,这并不意味着您犯了任何错误,它确实会让恶意软件动态解析其所有 API。
3、重建 IAT 并强制使用 OEP(原始入口点)。
4、如果您在查找 OEP 时遇到问题,请记住OEP 可能在 IAT 之后出现解决。在这种情况下,一种可能的方法是检查 IAT 是否已解析(检查 x64dbg 或 OllyDbg 上的模块化调用),或者在恶意软件的关键操作期间执行的关键 API 上设置断点CryptoAcquireContext(),因为当执行到达这些关键 API 时,IAT 肯定会得到解决。之后,建议寻找到特定内存地址的无条件跳转,甚至是间接调用(例如,调用 [eax])。另一种有趣的方法是使用调试器的图形可视化(在 x64dbg 上为 “g”)并在最后的“代码块”处检查这些转换点(内存地址的间接调用或无条件跳转)。最后,一个专门的工具可能会帮助您找出 OEP。正如你所注意到的,没有单一的方法可以做到这一点。
5、调整基址以匹配从内存中转储的段基址。
6、为了检测执行自我覆盖的恶意软件,我们可以尝试在.text/.code 部分。在这种情况下,我们可以选择在代码编写或执行期间触发此断点。
7、在两阶段解包情况下,第一个解包的二进制文件可能是 DLL。因此,取决于上下文中,将 DLL 二进制文件转换为可执行文件可能很有用,并且有很多方法可以完成此任务,但我最喜欢的方法是编辑 PE 标头以更改 Characteristics 字段,并将导出函数的条目作为入口点。
5.7 修复工具
1、PEBear 是由 Aleksandra Doniec(又名 Hasherezade)编写的一款出色的工具,用于可视化 PE 标头的详细信息并修复许多二进制问题。您可以从以下位置下载此工具: https://github.com/hasherezade/pe-bear-releases
2、Pestudio 是 Marc Ochsenmeier 编写的一款出色的工具,主要用于分类和收集潜在恶意软件的不同信息。该工具(免费和付费版本)可在此处获得:https://www.winitor.com/features
3、CFF Explorer 是 Explorer Suite 的一部分,它是一个著名的 PE 编辑器,用于可视化和修复 PE 标头。Explorer Suite 可从以下网址下载:https://ntcore.com/?page_id=388
4、pe_unmapper 是 Aleksandra Doniec(又名 Hasherezade)编写的另一个可以使用的工具用于将 PE 二进制文件从映射版本转换为未映射版本,从而修复所有 PE 对齐问题。该工具可从以下位置下载: https://github.com/hasherezade/libpeconv/tree/master/pe_unmapper
5、Scylla 是一个了不起的 x86/x64 导入重构器,已经嵌入到 x64dbg 中。如果您需要独立版本,可以从以下网址下载:https://github.com/NtQuery/Scylla
6、HxD 是一个出色的十六进制编辑器,例如,我们可以使用它来手动检查和修复 PE 标头。可以从以下网址下载: https://mh-nexus.de/en/hxd/
7、XVI32 Hex Editor 是另一个有趣的十六进制编辑器,它非常适合清理转储的内存区域以隔离解压的二进制文件。XVI32 十六进制编辑器可从以下位置下载: http://www.chmaas.handshake.de/delphi/freeware/xvi32/xvi32.htm
再次记住,解包脱壳只是恶意软件分析过程中的第一个障碍,许多其他困难的挑战,如字符串去混淆、API 解析、C2 配置提取、C2 仿真和其他主题也在列表中。该项目将在下一篇文章中介绍几种解包情况和所有这些提到的任务。
6. 代码注入审查
代码注入是 Window 系统上支持的操作,当然,它是一种非常有用的规避方法,因为恶意软件能够将恶意代码注入(写入)进程本身(自我注入)或远程(远程注入)的内存区域(有些人使用术语“段”)中,并且此有效负载将在目标上下文上执行,无论它是否成为它的一部分,并且不会留下很多证据。此外,当恶意负载继续在所谓的良好进程(例如,explorer.exe 和 svchost.exe)中运行时,源进程(恶意软件)可以干净地终止自身。最后,这是一种逃避安全防御的隐蔽方法。
6.1 注入技术
6.1.1 DLL 注入主要 API
OpenProcess()、VirtualAllocEx()、WriteProcessMemory 和 CreateRemoteThreat | NtCreateThread() | RtlCreateUserThread()
6.1.2 PE 注入主要 API
OpenThread(),SuspendThread(), VirtualAllocEx(), WriteProcessMemory(), SetThreatContext() 和 ResumeThreat() | NtResumeThread()
6.1.3 反射注入
CreateFileMapping()、Nt/MapViewOfFile()、OpenProcess()、memcpy() 和Nt/MapViewOfSection() 在最后,可以通过调用OpenProcess()、CreateThread()、NtQueueApcThread()、CreateRemoteThread() 或RtlCreateUserThread()。有趣的是,变体也可以使用VirtualQueryEx() 和 ReadProcessMemory()。
6.1.4 APC注入
这种代码注入技术允许程序通过附加到APC 队列在特定线程中执行代码。当线程退出可警报状态时(由 SleepEx()、SignalObjectAndWait()、MsgWaitForMultipleObjectsEx()、WaitForMultipleObjectsEx() 或 WaitForSingleObjectEx())等调用发起),将执行注入的代码。
因此,通常还可以看到CreateToolhelp32Snapshot()、Process32First()、Process32Next()、Thread32First()、Thread32Next()、QueueUserAPC() 和 KeInitializeAPC() 参与此技术。
6.1.5 挖空或替换进程
简而言之,恶意软件使用这种技术来“耗尽”进程的全部内容并在其中插入恶意内容。一些涉及的 API 包括CreateProcess()、NtQueryProcessInformation()、GetModuleHandle()、Zw/NtUnmapViewOfSection()、VirtualAllocEx()、WriteProcessMemory()、GetThreadContext()、SetThreadContext() 和 ResumeThread()。
6.1.6 AtomBombing
此技术是先前技术(APC 注入)的变体,其工作原理是将恶意负载拆分为单独的字符串,为每个给定字符串创建一个 Atom,将它们复制到 RW 段中(使用 GlobalGetAtomName() 和 NtQueueApcThread()),并使用 NtSetContextThread() 设置上下文。因此,其他 API 的列表是 OpenThread()、GlobalAddAtom()、GlobalGetAtomName() 和 QueueUserAPC()。
6.1.7 进程分身
这种技术可以作为流程空心化的一种演变来处理。这两种技术之间的主要区别在于,虽然 Process Hollowing 在恢复之前替换了进程的内容(图像),但 Process Doppelgänging 能够在创建进程之前替换图像,方法是在加载目标图像之前用恶意图像覆盖目标图像。这里的关键概念是 NTFS 操作在事务中执行,因此事务中的所有这些操作要么一起提交,要么不提交任何操作。同时,恶意图像只存在,它在事务内部可见,对任何其他进程都不可见。因此,恶意图像被加载到内存中,恶意软件从文件系统中删除恶意负载(通过回滚事务),因为该文件以前从未存在过。此技术涉及一些 API:
CreateTransaction()、CreateFileTransaction()、NtCreateSection、NtCreateProcessEx()、NtQueryInformationProcess()、NtCreateThreadEx() 和 RollbackTransaction()。
6.1.8 进程重影
Process Herpaderping:这种技术类似于 Process Doppelgänging,但有一个微妙的其程序的差异。进程 Herpaderping 基于这样一个事实,即安全防御通常通过在内核端使用PsSetCreateProcessNotifyRoutineEx() ) 或在驱动程序的 DispatchCleanup 例程期间(IRP_MJ_CLEANUP),该函数在创建线程后调用。这就是关键问题:如果攻击者创建并映射一个进程,然后,该攻击者能够修改文件映像,然后创建线程,因此安全产品能够检测到此类恶意负载。尽管如此,此检查顺序可以包括攻击者是否能够在磁盘上创建恶意二进制文件、打开其句柄、使用NtCreateSection 函数将其映射为图像部分(并包括 SEC_IMAGE 标志)、使用部分句柄(NtCreateProcesEx()) 创建进程、将文件内容修改为听起来不像恶意内容,并使用此“良好图像”创建线程 (NtCreateThreadEx())。关键是:当线程被创建时,会触发进程回调,并检查磁盘上文件的内容(好的),所以安全防御认为一切都很好,因为磁盘上的镜像是无害的,但真正的恶意在内存中。换句话说,安全防御无法有效地检测磁盘上不同于内存上的映像的此类映像。用于此技术的几个 API:CreateFile()、NtCreateSection()、NtCreateProcessEx() 和 NtCreateThreadEx ()。
6.1.9 hook注入
Hooking Injection:要使用这种技术,我们将看到涉及 Hooking 活动(如 SetWindowsHookEx() 和 PostThreadMessage() )的函数被用来注入恶意 DLL。
6.1.10 额外窗口内存注入
使用此技术,恶意软件威胁通过使用额外 Windows 内存(称为 EWM)将代码注入进程,其大小最大为 40 字节,并在注册 Windows 类期间附加类的实例。诀窍在于,附加的 spaced 足以存储一个指针,该指针可能会将执行转发给恶意代码。此技术涉及的一些可能的 API 包括FindWindowsA()、GetWindowThreadProcessId()、OpenProcess()、VirtualAllocEx()、WriteProcessMemory()、SetWindowLongPtrA() 和 SendNotify()。
6.1.11 传播注入
此技术已被 RIG Exploit Kit 和 Smoke Loader 等恶意软件威胁用于将恶意代码注入explorer.exe进程(中等完整性级别)和其他持久性进程中,它基于枚举(EnumWindows() → EnumWindowsProc → EnumChildWindowsPr() → EnumChildWindowsProc → EnumProps() → EnumPropsProc → GetProp) 窗口实现 SetWindowsSubclass() 的方法(有关https://docs.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrlsetwindowsubclass)。您可能还记得,此函数会安装 Windows 子类回调,并且如您所知,回调在安全领域中被解释为挂钩方法。它是如何工作的?找到子类化窗口后(检查 UxSubclassInfo 和/或 CC32SubclassInfo,它们提供子类标头),就可以保留旧的窗口过程,但我们也可以通过更新 CallArray 字段为窗口分配一个新过程。当事件发送到目标进程时,将调用新过程,然后,旧过程也会被调用(保持先前和预期的行为)。因此,恶意软件将恶意负载 (shellcode) 插入内存,并使用 SetPropA() 更新子类过程。调用此新属性(通过 windows 消息)时,执行将转发到有效负载。此技术涉及的一些 Windows API 包括FindWindow()、FindWindowEx()、GetProp()、GetWindowThreadProcessId()、OpenProcess()、ReadProcessMemory()、VirtualAllocEx()、WriteProcessMemory()、SetProp() 和 PostMessage()。
下面显示了来自恶意软件威胁的代码注入序列的一个非常常见的示例(IDA Pro 的反编译输出),当然,您将能够通过本节前面提供的信息来识别所使用的技术:
7. 脱壳方法
分类和主要描述解包技术相当复杂,但一般来说,有解压缩恶意软件样本的方法很少,例如使用调试器、自动化工具、Web 服务,甚至编写自己的解压缩代码来静态完成任务。选择的方法取决于特定的环境和情况。
7.1 调试器+特定函数断点
一个调试器+特定的函数断点。
这是最著名的方法,包括将恶意软件加载到调试器中并在众所周知的 API 上设置软件断点,其中大多数与内存管理和操作有关,以及查找要从内存中提取的可执行文件和/或 shellcode。使用 x64dbg/x32dbg(在其 CLI 上为 [ctrl]+g 或 bp)非常简单,可以在以下 API 上插入软件断点:
CreateProcessInternalW() VirtualAlloc() VirtualAllocEx() VirtualProtect( ) | ZwProtectVirtualMemory() WriteProcessMemory() | NtWriteProcessMemory() ResumeThread() | NtResumeThread() CryptDecrypt() | RtlDecompressBuffer() NtCreateSection() + MapViewOfSection() | ZwMapViewOfSection() UnmapViewOfSection() | ZwUnmapViewOfSection() NtWriteVirtualMemory() NtReadVirtualMemory()
7.2 脱壳注意事项(重点)
1、在恶意软件到达其入口点后(在系统断点之后)设置断点。
2、如前所述,建议使用反调试插件,在少数情况下,忽略从 0x00000000 到 0xFFFFFFFF 范围的所有异常(在 x64dbg 上,转到“选项”→“首选项”→“例外”以包含此范围)。
3、有时忽略异常可能是一个坏主意,因为恶意软件可能是它们调用解包过程。此外(在本文中脱离上下文)存在使用中断和异常来调用 API 的威胁。
4、通过使用 MSDN 了解所有列出的 API 及其各自的参数是成功解开恶意软件威胁的关键知识。
5、如果你使用的是VirtualAlloc(),建议在其出口点(ret 10)上设置断点。此外,有时通过设置写入内存断点可以更轻松地跟踪 dump 上分配的内容。
6、在某些情况下,恶意软件会将其有效负载提取到内存中,但会销毁 PE 标头,因此您将重建整个标题,尽管使用像 HxD 这样的十六进制编辑器的过程很简单。
7、提取的有效负载可能采用映射或非映射 格式。如果它是映射格式,那么可能 Import 表搞砸了,你需要通过 PEBear (最喜欢的方法) 手动重新对齐部分标题或使用 pe_unmapper 等工具来修复它们。您可能需要修复基址和入口点是否为零。
8、要重建已销毁的 IAT,建议使用 Scylla(嵌入在 x64dbg 上)。它必须输入 OEP,找到它的方法之一是查找由 jmp eax、call eax、call [eax] 等指令给出的代码转换。
9、很少有解压缩的恶意软件样本在 IAT 中没有任何功能,因此有两种可能性:要么部分未对齐(映射版本),要么解压缩的恶意软件动态解决其所有功能。
10、在 x64dbg 上使用“g” 热键可能有助于可视化块中的代码并找到到 OEP 的可能转换。
11、查找 OEP 的另一个不错的选择是通过 PIN 等代码插桩(https://www.intel.com/content/www/us/en/developer/articles/tool/pin-a-dynamic-binaryinstrumentation-tool.html)。
12、tiny_tracer (https://github.com/hasherezade/tiny_tracer)项目已废除 等工具使用 PIN 更轻松地执行检测,可用于了解恶意软件调用的函数(对于解压缩和了解反分析技术非常有用),还可以查找可能的 OEP。
13、在许多机会中,解压缩的代码可能只是恶意软件的第一阶段,因此有必要重复步骤来解压缩下一阶段。
14、很少有恶意软件示例执行自我覆盖,因此您可能必须在 .text 部分上设置断点以检测解压缩的二进制执行。在分析恶意软件时,很多恶意软件在执行完其恶意行为后会进行自我覆盖(即自我删除),以消除痕迹,使得分析和检测变得更加困难。因此,分析者可能需要在恶意软件的.text节区(即代码段)设置断点,以便在恶意软件解压缩并开始执行解压缩后的二进制代码时能够捕获到其行为。.text节区是程序代码存放的地方,设置断点可以帮助分析者监控和分析恶意软件的实际执行流程和行为模式
15、根据提取的二进制文件(例如 shellcode),它可能无法用完特定的进程上下文,因此有必要将其注入到正在运行的进程(例如,explorer.exe)中以执行进一步的分析。
16、如何检查提取的恶意软件是否可能是最终的恶意软件?没有明确的答案,通过从 DLL 中查找网络函数(如WS2_32.dll (Winsock) 和 Wininet.dll、纯文本字符串、加密函数(主要恶意软件是一种勒索软件)和许多其他证据。主要在重新对齐部分和/或重建 IAT 之后在 IDA Pro 上提取代码。
7.3 调试器+ DLL 加载中断
这是一种古老而简单的技术,通过在加载的每个 DLL 上停止调试器并检查内存中可能提取的 PE 格式文件的内存映射来解压缩恶意软件(注意:不要只关注 RWX 段,因为许多恶意软件在 RW 区域中提取其有效负载,并且在将执行上下文传输到提取的可执行文件之前不久,他们使用 VirtualProtect()将区域的权限更改为 RWX)。毫无疑问,它可能会消耗一些时间,但在许多情况下它仍然有效。常见调试器(x64dbg、OllyDbg 和 Immunity Debugger)具有一个配置选项,用于在每次 DLL 加载时中断。在x64dbg 上,此选项位于 Options → Preferences → Events 中,并标记 DLL 加载。在 OllyDbg 上,您可以转到“选项”→“调试选项”→“事件”并标记“在新模块 (DLL) 上中断”。
7.4 自动化脱壳工具
恶意软件分析师可以使用工具来自动化解包过程。Aleksandra Doniec (Hasherezade) 为实现此目标提供了出色的工具:
hollows_hunter:https://github.com/hasherezade/hollows_hunter/releases
PE-Sieve: https://github.com/hasherezade/pe-sieve/releases
mal_unpack:https://github.com/hasherezade/mal_unpack/releases
命令:
hollow_hunter.exe /pname <filename> /loop /imp mal_unpack.exe /exe <filename> /timeout <timeout: ms> pe-sieve64.exe /pid <process ID> pe-sieve64.exe /pid <process ID> /dmode 3 /imp 3
包含一些附加信息的解压缩二进制文件将保存到该工具创建的目录中。
7.5 进程脱壳
从内存中提取二进制文件的另一种简单(且有限)的方法是通过 Process Hacker 双击正在运行的进程,转到“内存”选项卡,查找有趣的区域/基址 (RWX),双击它并按“保存”按钮。当然,在自我注入的情况下,更容易找到恶意的二进制文件/payload。例如,在远程注入的情况下,您需要反转恶意软件以了解要注入的目标进程,或者进行“有根据的猜测”并在 explorer.exe 或 svchost.exe 等已知目标上查找注入的代码。同样,这是一种有限且简单的方法,但有时可以节省时间。
7.6 编写脱壳程序
虽然这种方法听起来很耗时,但编写 Python 代码来完成脱壳是很常见的,主要是在 shellcode 情况下,或者在处理恶意软件线程使用多种反 vm 和反调试技术的情况下。此外,我们还有一个优势,可以在处理类似的恶意软件案例时自动化解包过程。
8. 脱壳提取二进制文件
经过前边的各种知识铺垫,终于可以开始正式进行样本分析环节,看到的知识不会不要紧,遇到不会的才要学要查要实践。
8.1 IAT 表审查
首先将样本放到PE-bear进行查看是否存在有价值的信息:
我们开始是将样本丢过沙箱的,正常来说是会有网络相关DLL调用的,按现在来看很可能被加壳了。
因为是个dll文件,所以我们可以将它放到x64/32dbg进行分析,从导出表能看到它初始名字可能是cool.dll
8.2 加壳特征确认
我们再通过pestudio查看文件映射和内存映射的大小差别,.data段差别是很大的,又更加一步确定被加壳过了,当然不止这一点能确定,但文件与内存大小差距很大的基本是加壳过的。
8.3 调试器+断点脱壳
因为是dll文件,并且有五个导出函数,我们可以尝试加载第一个导出函数分析,x64dbg启动命令改为:
"C:\Windows\SysWOW64\rundll32.exe" C:\Users\Administrador\Desktop\sample_1.bin,#1
更改后要重新启动会话加载,到达程序入口点的时候使用快捷键ctrl+g下经典函数断点(断点附加说明在技术点问题整理中)
VirtualAlloc (在其出口点) VirtualProtect ResumeThread
下断点在virtualalloc的返回值处
在剩余两处下断点
第一次断点保存的内容目前看不出什么
直到第三次触发的内存地址发现了MZ头,但是为M8Z,这是aplib压缩的特征
我们转到内存布局进行保存
通过工具XVI32,CTRL+f查找字符串,之后往上找找就能看到正常的MZ头,我们只需要把MZ之前的数据清理掉就可以(辨别特征,修复方法,都是靠积累,没有什么可研究的不用钻牛角尖,平时做好笔记就可以)
修复完成
保存后再在PE-bear打开就能看到实际恶意程序的相关IAT表了,网络调用也出现了,剩下的便是找IDA老婆进行逆向分析具体行为功能了
9. 逆向分析恶意样本
首先我们要先记得这依旧是个DLL文件,所以它的功能基本是切割的,我们首先来解密他的外联数据获取恶意软件的url
9.1 解密数据获取IOC
我们来到Unexplored,之所以来看这里其实就是因为字符串没发现url或者IP相关,那他很可能没有解密,也很可能就在这个未知区域
我们能看到这里有pbData,并且有一个函数对这三个进行了交叉引用:
byte_10004000 (16 bytes)
pbData (8 bytes)
unk_10004018 (likely 0x2000 bytes)
这个时候我们先不管是做什么的,我们直接“x”来到交叉引用
我们可以看到三个数据都被作为参数传到了不同的子函数内。
这里第一个是分配内存,分配了2000h的内存
第二个函数我们看到了一些计算,目前看不出什么,记得有意义的函数进行改名备注格式:名字+自己标识+原子例程名
来到第三个子例程那就是加解密相关的了,我们得一步一步来了
看不懂没关系,首先他已经弃用了,之后这个函数就是获取或创建加密提供者(CSP),不理解没关系,继续往下走
CryptCreateHash函数:调用CSP句柄,SHA1算法(0x8004),0,0,新CSP哈希对象地址。整体来说就是我们知道了他用了SHA1算法,对句柄或者对象不太了解的先不管这个。
此函数整体就是添加数据到指定哈希对象
根据参数我们可以知道,对象句柄是新创建的,pbData是指针,也就是pbData处存放的八个字节,而这个dwDatalen是8,也就是说这可能在输出SHA1哈希并且被加密就是那八字节
生成密钥:
这里我们要根据他的参数进行对应解释
1、老csp句柄
2、一个 ALG_ID 结构,用于标识要为其生成密钥的 对称加密 算法,这里是0x6801,对应RC4
3、新CSP句柄
4、指定密钥类型,这里是0x280011,根据微软解释这里必须要用0x00800000将与其他任何dwFlags 结合使用, 预定义值以及按位OR 操作,看不懂没关系,我也看不懂,但我们要知道RC4密钥是40位也就是5字节(0x28)
5、新密钥的接收地址
我们来捋一下密钥顺序
首先他用pbData的8字节生成了一个SHA1值,之后SHA1作为KDF延伸出最终的RC4解密密钥
a. pbData -> C58B00157F8E9288(shift-e提取)
b. pbData -> SHA1(20字节)
c. SHA1 → CryptHashData() → CryptDeriveKey() → RC4 密钥(5 字节)
继续就是CryptDecrypt函数:
参数:
1、hkey,解密的密钥句柄,也就是最终的RC4密钥
2、哈希对象句柄,这里是0
3、布尔值,这里是1
4、0忽略
5、重点!要解密的缓冲区指针
意思就是解密a1区域长度为pdwDatalen的数据
长度基本不用想了就是2000,我们严谨点,假设为2000
剩下的要解密数据也不用想了对吧,就是剩下的unk_10004018,现在是无路可退。只能这样子推理了。
9.2 解密
首先我们将pbData进行sha1处理C58B00157F8E9288
接着我们记得RC4是5字节,也就是40位数,16进制1个是4位,取出来10个数11f668918f
提取一部分先
进行解密,ioc:
http://newnucapi.com/8/forum.php
http://gintlyba.ru/8/forum.php
http://stralonz.ru/8/forum.php
其实还有个更简单的办法,就是直接在CryptDecrypt() 上设置一个断点,找到指定最终解密内存区域,直接等结束提取内存就好了
9.3 行为功能分析
最难的结束了,来到简单的,基本跟着分析即可,一个函数一个函数的看就能知道导出函数的执行顺序逻辑了
还是比较麻烦的这个样本,但整体功能没啥难度了,这里就不细说了。
小密圈
扫描下方二维码
加入内部交流群
点分享
点收藏
点点赞
点在看