恶意软件作者使用了多种技巧,使恶意软件分析师的工作更加困难。这些技巧包括混淆技术来使逆向工程复杂化、反沙盒技术以避开沙盒、打包以绕过静态检测等等。多年来,许多在野生环境中使用的欺骗性技巧已被大量记录 文档化。然而,尽管有许多可用的技巧,典型的恶意软件中很少实现这些技巧。
本博文所讨论的后门我们称为 Roshtyak,并不是典型的恶意软件。Roshtyak 充满了各种技巧。有些是众所周知的,而有些则是我们以前未曾见过的。从技术角度来看,Roshtyak 为了保护自己所采取的措施非常有趣。Roshtyak 属于我们见过的最具保护性的恶意软件之一。我们希望通过发布我们对该恶意软件及其保护技巧的研究和分析,能够帮助其他研究人员识别和应对类似的技巧,并增强他们的分析环境,使其在面对上述逃避技术时更加抗能。
Roshtyak 是 Raspberry Robin 使用的 DLL 后门,Raspberry Robin 是一种通过感染的可移动驱动器传播的蠕虫。Raspberry Robin 非常普遍。今年我们保护了超过 55 万名用户免受该蠕虫的侵害。由于其高普遍性,我们并不是唯一注意到 Raspberry Robin 的人。
Red Canary 的研究人员在 2022 年 5 月发布了对 Raspberry Robin 的第一次分析。在 6 月,赛门铁克Symantec发布了一份关于挖矿/剪贴板劫持操作的报告,报道称网络犯罪分子从中至少获利 170 万美元。尽管赛门铁克没有将这一恶意操作与 Raspberry Robin 关联起来,但我们高确信地评估所分析的正是 Raspberry Robin。这一评估基于CampC重叠、强相关性恶意软件相似性以及在我们的遥测中观察到的共同感染。Cybereason、Microsoft以及Cisco在 2022 年 7 月/8 月发布了进一步的报告。Microsoft 报告称,Raspberry Robin 感染导致了 DEV0243又名 Evil Corp前勒索软件行为。我们无法确认这一连接,但我们认为 Raspberry Robin 感染的获利方式不止于挖矿有效载荷。其他 近期报道 则暗示 Raspberry Robin 和 Evil Corp 之间可能存在联系。
一张展示 Avast 保护的 Raspberry Robin 用户数量的地图
尽管已经有如此多的报告,关于 Raspberry Robin 仍有许多未知数。恶意软件背后的最终目标是什么?谁对 Raspberry Robin 负责?它是如何变得如此普遍的?不幸的是,我们并没有所有这些问题的答案。然而,我们可以回答一个我们多次看到的问题:重度混淆的 DLL或我们称之为 Roshtyak内部隐藏了什么功能?为了回答这个问题,我们完全逆向工程了一份 Roshtyak 样本,并将在这篇博文中呈现我们的分析结果。
概述
Roshtyak 被打包成多达 14 层保护,每一层均被严重混淆并且各有特定目的。一些工件表明,这些层最初可能是 PE 文件,但已转变为只有前一层才能解密和加载的自定义加密结构。在各层中散布着大量反调试、反沙盒、反虚拟机和反仿真检查。如果其中一个检查成功检测到分析环境,则会采取以下四种操作之一。
恶意软件调用 TerminateProcess 自己,以避免进一步表现出恶意行为,并保持后续层加密状态。Roshtyak 故意崩溃。这与自身终止效果相同,但由于 Roshtyak 的混淆特性,崩溃是否是故意的或由于发生错误不会立即清楚。恶意软件故意进入无限循环。由于循环本身位于混淆代码中,并且跨越数千条指令,因此很难确定该循环是否在执行有用的操作。最有趣的情况是,恶意软件在成功检查后会解压并加载虚假有效载荷。这发生在第八层,该层加载了数十个反分析检查。每个检查的结果都用于修改全局变量的值。第八层的数据区域加密了两个有效载荷:真实的第九层和虚假的有效载荷。若在所有检查执行后,全局变量的值符合预期,则真实的第九层会被解密。若至少有一个检查成功检测到分析环境,则全局变量的值将与预期值不符,这将导致 Roshtyak 解压并执行虚假的有效载荷。
虚假有效载荷是一个 BroAssist又名 BrowserAssistant广告软件样本。我们认为这个虚假有效载荷的目的是误导恶意软件分析师,使其认为样本的趣味性低于实际情况。当逆向工程师专注于快速解包样本时,可能看起来整个样本“仅仅”是一个混淆过的广告软件而且是一个非常旧的广告软件,这可能会导致分析师失去深入挖掘的兴趣。事实上,这些虚假有效载荷的戏弄效果可以非常有效。正如下面的屏幕截图所示,它至少误导了一名研究人员,该研究人员因虚假的 BrowserAssistant 有效载荷而错误地归因于 Raspberry Robin 蠕虫。
招数
为了使这篇博文在某种程度上简短且切中要点,我们直接详细说明 Roshtyak 使用的一些更有趣的逃避技术。
段寄存器
在执行的早期,Roshtyak 更喜欢使用不需要调用任何导入函数的检查。如果其中一个检查成功,样本便可以安静退出,而不会生成任何可疑的 API 调用。以下是 Roshtyak 检查 gs 段寄存器行为的一个示例。该检查旨在隐蔽,周围的垃圾指令使其易于被忽视。
这个检查的第一种思路是检测单步调试。在上述代码片段之前,cx 的值被初始化为 2。在 pop ecx 指令之后,Roshtyak 检查 cx 是否仍然等于 2。在正常情况下,该值应该通过栈和 gs 寄存器传播。然而,单步事件将重置 gs 选择符的值,导致 ecx 被弹出时的值不同。
但这个检查还有更多内容。作为上述两个 push/pop 配对的副作用,gs 的值一度被改为 2。在这次检查之后,Roshtyak 进入一个循环,计算 gs 不再是 2 的迭代次数。gs 选择符在线程上下文切换后也会重置,因此该循环实际上是在计算上下文切换发生之前的迭代次数。Roshtyak 反复执行此过程,汇总结果并检查是否属于裸金属执行环境的合理范围。如果样本在一个 hypervisor 或者模拟器下运行,平均迭代次数可能会超出这个范围,这使得 Roshtyak 能够检测不期望的执行环境。
Roshtyak 还检查 cs 段寄存器的值是否为 0x1b 或 0x23。在这里,0x1b 是在本地 x86 Windows 运行时的预期值,而 0x23 是在 WoW64 下的预期值。
随机 ntdll 硬编码的 APC 注入
Roshtyak 从单独的进程中执行其部分功能。例如,在与其 CampC 服务器通信时,它会生成一个看似无辜的进程,如 regsvr32exe。通过共享区域,它将其通信模块注入到新进程的地址空间中。该注入模块通过 APC 注入执行,使用 NtQueueApcThreadEx。
有趣的是,ApcRoutine 参数标记要调度执行的目标例程并不指向注入模块的入口点。相反,它指向 ntdll 内部的一个看似随机地址。仔细观察,我们可以看到这个地址并不是随机选择的,而是 Roshtyak 在 ntdll 的代码段中扫描 pop r32 ret gadgets排除了 pop esp,因为转变栈位置是不可取的并随机选择了一个用作 ApcRoutine。
查看 ApcRoutine 的调用约定可以揭示事情的真相。pop 指令使栈指针指向 NtQueueApcThreadEx 的 SystemArgument1 参数,因此 ret 指令实际上跳转到 SystemArgument1 所指向的任意位置。这意味着通过滥用此 gadget,Roshtyak 可以将 SystemArgument1 视为 APC 注入的入口点。这极大混淆了控制流,并使得 NtQueueApcThreadEx 的调用看起来更合理。如果有人对该函数进行了钩子并检查 ApcRoutine 参数,发现它指向 ntdll 代码区域,可能就会让他们相信该调用不是恶意的。
在写入组合内存上检查读/写性能
在下一个检查中,Roshtyak 分配了一个使用 PAGEWRITECOMBINE 标志的大内存缓冲区。该标志会修改缓存行为,以优化顺序写入性能牺牲读取性能和可能的内存排序。Roshtyak 使用此方法检测其是否运行在物理机器上。它进行了一项实验,首先写入分配的缓冲区,然后再从中读取,同时利用一个独立的线程进行计时。该实验重复进行 32 次,只有在大部分时间内写入性能至少高于读取性能六倍时,此检查才被视为通过。如果检查失败,Roshtyak 会故意选择错误的 RC4 密钥,导致无法正确解密下一层。
隐藏 shellcode
被注入的 shellcode 也被巧妙地隐藏。当 Roshtyak 准备进行代码注入时,它首先创建一个大型区域并将其映射到当前进程作为 PAGEREADWRITE。然后,它用随机数据填充该区域,并将 shellcode 放在随机偏移量内。由于 shellcode 只是一个相对较小的加载器,后跟看起来随机的打包数据,因此整个区域显得像是随机数据。
该区域随后从当前进程中解除映射,并映射到目标进程,其中使用上述 APC 注入技术执行。添加随机数据旨在掩盖 shellcode 的存在。仅从目标进程的内存转储来看,该区域看起来充满了随机数据,且并不包含任何有效的可执行代码。即使有人怀疑在该区域某处存在有效代码,但也不容易找到它的确切位置。
Ret2Kernel32
Roshtyak 会尽量做好自我清理工作。每当某个字符串或内存不再需要时,Roshtyak 会擦除和/或释放它,以尽量消灭更多证据。对于 Roshtyak 的层也是如此。每当某一层完成其工作时,它会在将控制传递给下一层之前释放自身。然而,该层不能直接简单地释放自身。如果它在当前执行的内存区域上调用 VirtualFree,整个过程将崩溃。
因此,Roshtyak 通过在层间转换期间执行 ROP 链来释放该层,以避免这个问题。当一层即将退出时,它在栈上构建一个 ROP 链并返回到其中。以下是一个此类 ROP 链的示例。此链首先返回到 VirtualFree 和 UnmapViewOfFile 以释放前一层的内存。然后返回至下一层。下一层的返回地址被设置为 RtlExitUserThread,以保障执行。
MulDiv bug
MulDiv 是 kernel32dll 导出的一个函数,它接收三个有符号 32 位整数作为参数。它将前两个参数相乘,然后返回将乘法结果除以第三个参数并取整后的最终结果。虽然这个函数看起来很简单,但在微软的实现中存在一个古老的符号扩展bug。这个 bug 现在被视为特性,并且可能永远不会被修复。
Roshtyak 意识到这个 bug,并通过调用 MulDiv(1 0x80000000 0x80000000) 来检测是否存在该 bug。在真实的 Windows 机器上,这会触发 bug,而 MulDiv 错误地返回 2,尽管正确的返回值应该是 1,因为 (1 2147483648) / 2147483648 = 1。这允许 Roshtyak 检测不复制此 bug 的模拟器。例如,这成功检测了 Wine,有趣的是,它包含一个不同的 bug,使得上述调用返回 0。
干扰栈上存储的返回地址
还有一些技巧用于混淆函数调用。如前节所示,Roshtyak 喜欢使用 ret 指令来调用函数。下一个技巧类似,因为它也操控了栈,以便使用 ret 指令跳转到所需地址。
为此,Roshtyak 在当前线程的栈中搜索指向之前某一层代码区域的指针与其他层不同,该层并未通过 ROP 链技术释放。它将所有这些指针替换为其想要调用的地址。然后,代码会多次返回,直到 ret 指令遭遇其中一个被劫持的指针,从而将执行重定向到所需地址。
基于异常的检查
此外,Roshtyak 包含一些设置自定义向量异常处理程序的检查,并故意触发各种异常,以确保它们都能如预期处理。
Roshtyak 使用 RtlAddVectoredExceptionHandler 设置向量异常处理程序。此处理程序包含针对特定异常代码的自定义处理程序。通过 SetUnhandledExceptionFilter 还注册了一个顶级异常处理程序。此处理程序在目标执行环境中不应被调用所有故意触发的异常都不应经过向量异常处理程序。因此,此顶层处理程序仅包含一个 TerminateProcess 的调用。有趣的是,Roshtyak 还使用 ZwSetInformationProcess 设置 SEMFAILCRITICALERRORS,这是 ProcessDefaultHardErrorMode 类。这样,即使异常以某种方式传递到默认异常处理程序,Windows 也不会显示标准错误消息框,这可能会提醒受害者有所怀疑。
一切准备就绪后,Roshtyak 开始生成异常。第一个异常是在 popf 指令后直接生成的,后面跟着 cpuid 指令如下所示。通过 popf 指令弹出的值经过精心制作,使 Trap 标志被设置,这反过来会引发单步异常。在物理机器上,异常应该会在 cpuid 指令执行后立刻触发。然后,自定义向量异常处理程序将接管并将指令指针移开标记无效指令的 C7 B2 操作码。然而,在许多 hypervisors 中,单步异常不会被触发。这是因为 cpuid 指令强制产生 VM 退出,可能会延迟 Trap 标志的影响。如果是这种情况,处理器在尝试执行无效操作码时将引发非法指令异常。如果向量异常处理程序遇到此类异常,它便知道自己在 hypervisor 下运行。此技术的变体在 Palo Alto Networks 的博客中有详细描述。请参考该博客获取更多细节。
另一个异常使用两字节 int 3 指令CD 03生成。此指令后跟无效操作码。这条 int 3 指令引发一个断点异常,由向量异常处理程序处理。有趣的是,向量异常处理程序实际上并不处理异常。这是因为在 Windows 处理两字节 int 3 指令时,默认情况下会将指令指针留在两条指令字节之间,指向 03 字节。当从此 03 字节反汇编时,垃圾操作码突然变得有意义。我们认为这是针对一些过于热心的调试器的检查,它们可能会将指令指针“修复”为指向 03 字节后面的位置。
此外,向量异常处理程序检查线程的 CONTEXT,确保寄存器 Dr0 到 Dr3 是空的。如果它们不为空,说明该进程正在使用硬件断点进行调试。虽然这个检查在恶意软件中相对常见,但通常通过调用诸如 GetThreadContext 的函数获得 CONTEXT。而在这里,恶意软件作者利用了 CONTEXT 作为参数传递给异常处理程序,因此不需要调用任何额外的 API 函数。
大型可执行映射
接下来的检查主要因为我们不太确定它的具体目的而显得有趣换句话说,我们乐意听听你的理论!。它从 Roshtyak 创建一个大小为 0x386F000 的大 PAGEEXECUTEREADWRITE 映射开始。然后它在自己的地址空间中将此映射映射了九次。之后,它对这个映射进行了 memset,填充 0x42inc edx 的操作码,除了最后六个字节,它们装入四条 inc ecx 指令和 jmp dword ptr [ecx]见下文。接下来,它将映射视图的九个基础地址放入一个数组,然后将一个单独的 ret 指令的地址放入此数组中。最后它将 ecx 指向这个数组,并调用第一个映射视图,结果依次调用所有映射视图,直到最后的 ret 指令。返回后,Roshtyak 验证 edx 被恰好增加了 0x1FBE6FCA 次9 (0x386F000 6)。
我们最好的推测是,这又是一个反模拟器检查。例如,在一些模拟器中,映射区可能没有完全实现,因此写入到一个映射视图的指令可能不会传递到其他八个实例。另一个理论是,此检查可能是为了请求大量内存,而模拟器可能无法满足这一要求。毕竟,所有视图的总大小几乎占据了标准 32 位用户模式地址空间的一半。
检测进程挂起
此技巧滥用 NtCreateThreadEx 中的一个未文档化线程创建标志,以检测 Roshtyak 的主进程何时被外部挂起这可能意味着附加了调试器。该标志本质上允许线程在调用 PsSuspendProcess 后继续运行。这与另一个技巧相结合,利用线程挂起计数器是一个带符号的 8 位值,这意味着它的最大值是 127。Roshtyak 创建两个线程,其中一个线程不断挂起另一个线程,直到挂起计数器达到上限。之后,第一个线程定期挂起另一个线程,并检查调用 NtSuspendThread 是否不断返回 STATUSSUSPENDCOUNTEXCEEDED。如果没有返回此错误代码,则该线程肯定被外部挂起并恢复这将使挂起计数器保持在 126,因此下一个调用 NtSuspendThread 将成功。如果没有收到此错误代码,Roshtyak 认为可疑,便使用 TerminateProcess 退出。整个技术在 Secret Club 的博客中有更详细的描述。我们认为 Roshtyak 的作者可能就是从那里获得这个技巧的。值得一提的是,Roshtyak 仅在 Windows 1832319H1及之后的版本上使用此技术,因为在此之前未实施未文档化的线程创建标志。
间接注册表写入
Roshtyak 执行许多可疑的注册表操作,例如,为持久性设置 RunOnce 键。由于对这些键的修改可能会被监控,Roshtyak 尝试规避监控。它首先生成一个随机的注册表键名称,并使用 ZwRenameKey 将 RunOnce 键临时重命名为随机名称。重命名后,Roshtyak 在临时键中添加一条新的持久性条目,然后将其重命名回 RunOnce。这种写入注册表的方法很容易被检测到,但它可能规避一些简单的基于钩子的监控方法。
海鸥加速app官网版同样,Roshtyak 还有多种方法用于删除文件。除了明显的 NtDeleteFile 函数调用外,Roshtyak 还能够通过在调用 ZwSetInformationFile 时设置 FileDispositionInformation 或 FileRenameInformation 有效地删除文件。然而,与注册表修改方法不同,这似乎并不是为了逃避检测而实现的。相反,如果初始 NtDeleteFile 调用失败,Roshtyak 将尝试这些替代方法。
检查 VBAWarnings
VBAWarnings 注册表值控制 Microsoft Office 在用户打开包含嵌入式 VBA 宏的文档时的行为。如果此值为 1意味着“启用所有宏”,则即便不需用户交互,宏会默认执行。这是沙盒中经常使用的设置,其设计目标就是自动引爆恶意文档。另一方面,这个设置对于普通用户并不常见,因为他们通常不会改变随机设置以使自己更脆弱至少大多数人不会。因此,Roshtyak 使用这个检查来区分沙盒和普通用户,如果 VBAWarnings 的值为1,它将拒绝进一步运行。有趣的是,这意味着出于某种原因降低安全性的用户将不受 Roshtyak 的影响。
清除命令行
Roshtyak 的核心用非常可疑的命令行执行,例如 RUNDLL32EXE SHELL32DLLShellExecRunDLL REGSVR32EXE U /s CUsersltREDACTEDgtAppDataLocalTempdpcwetl。这些命令行看起来并不特别合理,因此 Roshtyak 尝试在执行过程中隐藏它们。它通过擦除从各个来源收集的命令行信息来做到这一点。它首先调用 GetCommandLineA 和 GetCommandLineW,并擦除两个返回的字符串。然后它尝试擦除指向 PEBgtProcessParametersgtCommandLine 的字符串即使这个指针指向的字符串已经被擦除。由于 Roshtyak 通常在 WoW64 下运行,因此它还调用 NtWow64QueryInformationProcess64 来获取指向 PEB64 的指针,从而擦除通过遍历这个“第二” PEB 得到的 ProcessParametersgtCommandLine。虽然擦除命令行可能旨在让 Roshtyak 看起来更合法,但命令行完全缺失也是非常不寻常的。Red Canary 的研究人员在他们的 博客 中注意到了这一点,并提出基于这些可疑空命令行的检测方法。
其他技巧
除了到目前为止描述的技术外,Roshtyak 还使用了许多其他恶意软件中常见的不太复杂的技巧。这些包括:
使用 ThreadHideFromDebugger 隐藏线程并使用 NtQueryInformationThread 验证线程确实被隐藏修补 ntdll 中的 DbgBreakPoint使用 GetLastInputInfo 检测用户不活跃状态检查 PEB 字段BeingDebugged NtGlobalFlag检查 KUSERSHAREDDATA 字段KdDebuggerEnabled ActiveProcessorCount NumberOfPhysicalPages检查所有正在运行的进程的名称有些通过哈希对比,有些通过模式,有些通过字符分布对比哈希所有已加载模块的名称,并与硬编码的黑名单进行检查验证主进程名称是否不太长且不匹配沙盒中使用的已知名称使用 cpuid 指令检查 hypervisor 信息和处理器品牌使用文档不全的 COM 接口将用户名和计算机名与硬编码黑名单进行检查检查已知沙盒伪装文件的存在检查自身适配器的 MAC 地址是否与硬编码黑名单匹配从 ARP 表中检查 MAC 地址使用 GetBestRoute 填充并使用 GetIpNetTable 检查调用 ZwQueryInformationProcess 以获取 ProcessDebugObjectHandle、ProcessDebugFlags 和 ProcessDebugPort检查显示设备的 DeviceId使用 EnumDisplayDevices检查 PhysicalDrive0 的 ProductId使用 IOCTLSTORAGEQUERYPROPERTY检查虚拟硬盘使用 NtQuerySystemInformation 和 SystemVhdBootInformation检查原始 SMBIOS 固件表使用 NtQuerySystemInformation 和 SystemFirmwareTableInformation设置 Defender 排除路径与进程删除与恶意软件使用的进程名称相关的 IFEO 注册表键混淆
我们已经展示了许多旨在防止 Roshtyak 在不期望的执行环境中引爆的反分析技巧。仅凭这些技巧很容易进行修补或绕过。使分析 Roshtyak 特别致命的是这些技巧与重度混淆和多层打包的组合。这使得静态研究反分析技巧并找出如何通过所有检查以使 Roshtyak 自解压变得非常困难。此外,即使是主要有效载荷也进行了同样的混淆,这意味着,静态分析 Roshtyak 的核心功能也需要大量去混淆工作。
在本节的剩余部分,我们将介绍 Roshtyak 使用的主要混淆方法。
控制流平坦化
控制流平坦化是 Roshtyak 使用的最显著的混淆技术之一。它以不寻常的方式实施,使 Roshtyak 函数的控制流图具有独特的外观见下文。控制流平坦化的目标是模糊各个代码块之间的控制流关系。
控制流由一个 32 位控制变量来指引,该变量跟踪执行状态,以识别待执行的代码块。每个函数开始时,控制变量被初始化为指向起始代码块通常是 nop 块。然后在每个代码块的末尾修改此控制变量,以识别应执行的下一个代码块。此修改通过某些算术指令来执行,例如 add、sub 或 xor。
存在一个调度程序使用控制变量将执行路由到正确的代码块。此调度程序由 if/else 块构成,并循环连接成一个循环。每个调度程序块获取控制变量并使用算术指令对此进行屏蔽,查看是否应将执行路由到它所维护的代码块。有趣的是,这里存在多个进入点使代码块可以指向调度循环,赋予控制流图锯齿状的“锯刀”外观。
分支使用一个特殊的代码块,该代码块包含 imul 指令。它依赖前一个块计算分支标志。此分支标志使用 imul 指令乘以一个随机常数,结果被加法、减法或异或运算到新的控制变量。由此导致的,是在分支块之后,控制变量将根据为分支标志计算的值标识两个可能的后续代码块之一。
函数激活密钥
Roshtyak 的混淆函数期望一个额外的参数,我们称之为激活密钥。此激活密钥用于解密所有本地常数、字符串、变量等。如果函数使用错误的激活密钥进行调用,则解密结果是垃圾明文,很可能导致 Roshtyak 在控制流调度程序中陷入无限循环。这是因为所有由调度程序使用的常数控制变量的初始值、调度程序块的屏蔽值以及用于跳转到下一个代码块的常量都使用激活密钥进行加密。没有正确的激活密钥,调度程序根本不知道如何进行调度。
没有知道正确的激活密钥就几乎不可能逆向工程一个函数。所有字符串、缓冲区和局部变量/常量仍然是加密的,所有交叉引用丢失,更糟糕的是,没有控制流信息。仅剩下个别的代码块,但无法确定它们之间的关系。
每个混淆函数必须从某处被调用,这意味着调用该函数的代码必须提供正确的激活密钥。然而,获取激活密钥并不容易。首先,调用目标也使用激活密钥加密,因此如果不知道正确的激活密钥,就无法找到函数的调用来源。其次,即使提供的激活密钥也是加密的,且由调用函数的激活密钥进行了加密。而该激活密钥被加密的又是下一个调用函数的激活密钥。以此类推,递归进行,直至入口点函数。
这使我们可以讨论如何去混淆这堆混沌。入口点函数的激活密钥必须是明文。使用此激活密钥,可以解密直接从此入口点函数调用的函数的调用目标和激活密钥。递归应用这个方法,使我们能够重建完整的调用图及所有函数的激活密钥。唯一的例外是那些从未被调用且由编译器留下的函数。这些函数可能依然是个谜,但由于样本并未使用它们,因此它们对恶意软件分析师而言并不重要。
变量掩码
一些变量并不以明文形式存储,而是使用一个或多个算术指令进行了掩码。这意味着,如果 Roshtyak 并未主动使用某个变量,则其会保持变量的值为混淆形式。每当 Roshtyak 需要使用此变量时,必须首先将其解码,然后才能使用。相反,在使用变量后,Roshtyak 会将其转换回掩码形式。这种基于掩码的混淆方法稍微增加了调试时跟踪变量的复杂性,并使查找已知变量值的内存变得更加困难。
循环变换
Roshtyak 在某些循环条件上表现得很有创意。它并不以简单的形式编写循环,例如 for (int i = 0 i lt 1690 i),而是将循环变形为例如 for (int32t i = 0x06AB91EE i != 0x70826068 i = i 0x509FFFF 0xEC891BB1)。虽然这两个循环都会执行恰好 1690 次,但第二种形式更难以阅读。乍一看,第二个循环执行多少次并不清楚甚至是否完成。在调试过程中,跟踪第二种情况下的循环迭代次数也更加困难。
打包
如前所述,Roshtyak 的核心隐藏在多个打包层之后。虽然所有层似乎都最初编译为 PE 文件,但除了严格必要的数据入口点、节、导入和重定位外,其他数据均被剥离。此外,Roshtyak 支持两种用于存储剥离 PE 文件信息的自定义格式,层层交替使用不同格式。此外,自定义格式的某些部分是加密的,有时使用基于各种反分析检查结果生成的密钥。
这使得从静态角度解包 Roshtyak 的层成为一个具有挑战性的任务。首先,必须反向工程自定义格式,以找出如何解密加密部分。然后,需要重建 PE 头、节、节头以及导入表因为重定位表可以简单地关闭,所以不需要重建。虽然这一切都是可行的并且可以使用像 LIEF 这样的库简化,但可能会耗费大量时间。加上这些层有时是相互依赖的,可能更容易在内存中动态分析 Roshtyak。
其他混淆技术
除了上述技术,Roshtyak 还使用其他混淆技术,包括:
垃圾指令插入导入哈希化频繁内存擦除混合布尔算术混淆冗余线程重度多态性核心功能
现在我们已经描述了 Roshtyak 如何自我保护,了解其实际功能也许会引起兴趣。Roshtyak 的 DLL 相对较大,超过一兆字节,但是一旦你消除所有的混淆,其功能实际上相当简单。它的主要目的是下载进一步执行的有效载荷。此外,它还进行一些恶行,比如建立持久性、升级权限、横向移动以及提取受害者的信息。
持久性
Roshtyak 首先在 SystemRootTemp 生成一个随机文件名,并将其 DLL 映像移到那里。生成的文件名由两个到八个随机小写字符与从硬编码列表中选择的随机扩展名相连。用于生成此文件名的 PRNG 是以 C 的卷序列号为种子。我们分析的样本硬编码了七种扩展名log、tmp、loc、dmp、out、ttf 和 etl。我们观察到其他样本中使用了不同的扩展名,表明该列表在某种程度上是动态的。在某个小概率下,Roshtyak 还会使用随机生成的扩展名。构建完成后,Roshtyak DLL 的完整路径可能类似于 CWindowsTempwcdpetl。
一旦 DLL 映像移动到新的文件系统路径,Roshtyak 会将其 Modified 时间戳篡改为当前系统时间。接下来,它会设置一个 RunOnce(Ex) 注册表项以建立持久性。该注册表条目采用前述的间接写入注册表技巧创建。插入到键中的命令可能如下所示:
RUNDLL32EXE SHELL32DLLShellExecRunDLL REGSVR32EXE U /s CWindowsTempwcdpetl
需要注意几件事。首先,regsvr32 不关心加载 DLL 的扩展名,允许 Roshtyak 隐藏在类似 log 的无辜扩展名下。其次,/s 参数将 regsvr32 置于静默模式。否则,regsvr32 会抱怨找不到名为 DllUnregisterServer 的导出。最后,注意命令路径末尾的句点。在路径规范化过程中,这个句点会被 移除,因此对命令几乎没有效果。我们不完全确定包含这个句点字符的作者最初意图是什么。它看起来是想骗一些反恶意软件软件,让其无法将持久性条目与文件系统上的有效载荷连接起来。
默认情况下,Roshtyak 使用 HKCUSOFTWAREMicrosoftWindowsCurrentVersionRunOnce 键进行持久性。然而,在某些情况下例如,当它通过检查进程 avpexe 发现 Kaspersky 正在运行时,将使用 HKLMSOFTWAREMicrosoftWindowsCurrentVersionRunOnceEx 键。RunOnceEx 键能够加载 DLL,因此当使用此键时,Roshtyak 直接指定 shell32dll,省略了 rundll32 的使用。
权限升级
Roshtyak 使用 UAC 绕过和常规 EoP 漏洞,试图提升特权。与许多其他恶意软件不同,后者只是盲目执行可能找到的 UAC 绕过或漏洞,Roshtyak 则努力判断权限升级方法是否可能成功。这大概是为了降低因不必要地使用不兼容的绕过或漏洞而导致的检测几率。对于 UAC 绕过来说,这涉及检查 ConsentPromptBehaviorAdmin 和 ConsentPromptBehaviorUser 注册表键。对于 EoP 漏洞而言,则检查 Windows 版本号和补丁级别。
除了检查 ConsentPromptBehavior(AdminUser) 键外,Roshtyak 还执行其他合理性检查,以确保应该继续进行 UAC 绕过。即,它通过使用带有 SID S1532544DOMAINALIASRIDADMINS的 CheckTokenMembership 检查管理员权限。它还检查 KUSERSHAREDDATASharedDataFlags 中的 DbgElevationEnabled 标志。此标志是一个未记录的 标志,当 UAC 启用时被设置。如果检测到任何一个反病毒软件,Roshtyak 会避免某些 UAC 绕过技术,推测可能会导致检测的技术。
至于实际的 UAC 绕过,已实现两种主要方法。第一种是来自 UACMe 的正确名称为 ucmDccwCOM 的实现。有趣的是,当这种方法被执行时,Roshtyak 会临时伪装其进程为 explorerexe,通过在对应于主可执行模块的 [LDR