Обход антивируса в Meterpreter

Обойти антивирус

Здорова народ! В сегодняшней статье я покажу, как обойти антивирус при пентесте используя фреймворк Metasploit.

Еще по теме: Как обойти антивирус с помощью Chimera

Обход антивируса в Meterpreter

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

shellcode_inject.exe не содер­жит угроз
shellcode_inject.exe не содер­жит угроз

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

Cоз­дание авто­ном­ного (не staged) шелл‑кода. Выг­лядит безобид­но
Cоз­дание авто­ном­ного (не staged) шелл‑кода. Выг­лядит безобид­но

Гля­дя на содер­жимое meter.txt, я бы ско­рее решил, что это стро­ка в Base64, чем шелл‑код.

Сто­ит отме­тить, что мы исполь­зовали шелл‑код meterpreter_reverse_tcp, а не meterpreter/reverse_tcp. Это авто­ном­ный код, который содер­жит в себе все фун­кции Meterpreter, он ничего не будет ска­чивать по сети, сле­дова­тель­но, шан­сов спа­лить­ся у нас будет мень­ше. Но вот связ­ка shellcode_inject.exe и meter.txt уже пред­став­ляет опас­ность. Давай пос­мотрим, смо­жет ли анти­вирус рас­познать угро­зу?

Ин­жект Meterpreter
Ин­жект Meterpreter

Об­рати вни­мание: мы исполь­зовали для инжекта кода сис­темный про­цесс, он сра­зу работа­ет в кон­тек­сте System. И похоже, что наш подопыт­ный анти­вирус хоть в кон­це и руг­нулся на shellcode_inject.exe, но все же про­пус­тил дан­ный трюк.

Вы­пол­нение вре­донос­ного кода в обход анти­виру­са
Вы­пол­нение вре­донос­ного кода в обход анти­виру­са

За­пус­тив что‑то вро­де Meterpreter, ата­кующий получит воз­можность выпол­нить полез­ную наг­рузку пря­мо в памяти, минуя тем самым HDD.

За­пуск вре­донос­ного ПО через Meterpreter в памяти
За­пуск вре­донос­ного ПО через Meterpreter в памяти

Мно­го лет этот прос­той трюк выручал меня. Сра­ботал он и на этот раз.

Code caves

Каж­дый exe-файл (PE-фор­мат) содер­жит код. При этом весь код офор­млен в виде набора фун­кций. В свою оче­редь, фун­кции при ком­пиляции раз­меща­ются не одна за дру­гой вплот­ную, а с некото­рым вырав­нивани­ем (16 байт). Еще боль­шие пус­тоты воз­ника­ют из‑за вырав­нивания меж­ду сек­циями (4096 байт). И бла­года­ря всем этим вырав­нивани­ям соз­дает­ся мно­жес­тво неболь­ших «кодовых пус­тот» (code caves), которые дос­тупны для записи в них кода. Тут все очень силь­но зависит от ком­пилято­ра. Но для боль­шинс­тва PE-фай­лов, а нас глав­ным обра­зом инте­ресу­ет ОС Windows, кар­тина может выг­лядеть при­мер­но так, как показа­но на сле­дующем скрин­шоте.

Гра­фичес­кое пред­став­ление рас­положе­ния фун­кций и пус­тот меж­ду ними
Гра­фичес­кое пред­став­ление рас­положе­ния фун­кций и пус­тот меж­ду ними

Что пред­став­ляет собой каж­дая такая «пус­тота»?

Code cave
Code cave

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

По­иск кодовых пус­тот в исполня­емом фай­ле
По­иск кодовых пус­тот в исполня­емом фай­ле
Ви­зуаль­ное рас­положе­ние пус­тот по все­му фай­лу
Ви­зуаль­ное рас­положе­ние пус­тот по все­му фай­лу

В этом при­мере мы поис­кали толь­ко 12-бай­тные пус­тоты, так что реаль­ное их количес­тво будет гораз­до боль­шим. Пус­тот хоть и немало, но их явно недос­таточ­но для раз­мещения пол­ноцен­ной прог­раммы. Поэто­му дан­ный спо­соб годит­ся толь­ко для встав­ки шелл‑кодов, раз­мер которых ред­ко пре­выша­ет 1 Кбайт.

Да­вай пос­мотрим, смо­жем ли мы раз­ложить неболь­шой мно­гос­тупен­чатый Windows/Meterpreter/reverse_tcp шелл‑код по этим пус­тотам. Раз­мер code cave ред­ко пре­выша­ет 16 байт, так что нам пот­ребу­ется раз­бивать шелл‑код силь­нее, чем по базовым бло­кам. Сле­дова­тель­но, при­дет­ся встав­лять еще и допол­нитель­ные jmp-инс­трук­ции для их свя­зи и кор­ректи­ровать адре­са условных перехо­дов. На деле это дос­таточ­но рутин­ная опе­рация.

Фраг­мента­ция и встав­ка шелл‑кода в code caves
Фраг­мента­ция и встав­ка шелл‑кода в code caves

Обход антивируса

В резуль­тате наш шелл‑код раз­мером в 354 бай­та был раз­бит на 62 кусоч­ка и помещен в ран­домные пус­тоты меж­ду фун­кци­ями.

Ис­ходный исполня­емый файл — пус­тота меж­ду фун­кци­ями из‑за вырав­нивания
Ис­ходный исполня­емый файл — пус­тота меж­ду фун­кци­ями из‑за вырав­нивания
Мо­дифи­циро­ван­ный исполня­емый файл — заражен­ная пус­тота меж­ду фун­кци­ями
Мо­дифи­циро­ван­ный исполня­емый файл — заражен­ная пус­тота меж­ду фун­кци­ями

По идее, такой под­ход дол­жен дать нам полимор­физм, так как каж­дый раз шелл‑код будет помещать­ся в слу­чай­ные пус­тоты по две‑три инс­трук­ции (это называ­ется умным сло­вом «пер­мутация»). Даже на уров­не трас­сы исполне­ния код будет обфусци­рован из‑за дос­таточ­но боль­шого количес­тва инс­трук­ций jmp меж­ду фраг­мента­ми.

Ори­гиналь­ная трас­са исполне­ния шелл‑кода
Ори­гиналь­ная трас­са исполне­ния шелл‑кода
Об­фусци­рован­ная трас­са исполне­ния шелл‑кода
Об­фусци­рован­ная трас­са исполне­ния шелл‑кода

C помощью это­го спо­соба мы можем обой­ти таким обра­зом мно­го «прос­тых» анти­виру­сов.

Вы­пол­нение вре­донос­ного шелл‑кода в обход анти­виру­са
Вы­пол­нение вре­донос­ного шелл‑кода в обход анти­виру­са

Обход антивируса Meterpreter

Од­нако серь­езные анти­вирус­ные про­дук­ты таким трю­ком все же не про­ведешь.

Crypt

Как ни стран­но, клас­сичес­кий xor исполня­емо­го фай­ла с динами­чес­ким клю­чом все еще успешно работа­ет про­тив даже самых гроз­ных анти­виру­сов. Для при­мера возь­мем какой‑нибудь очень палев­ный исполня­емый файл и зак­рипту­ем прос­тым xor все, что толь­ко мож­но. Крипт сек­ций .text и .data выг­лядит при­мер­но так.

Xor с клю­чом 0x77 ука­зан­ных адре­сов и раз­меров
Xor с клю­чом 0x77 ука­зан­ных адре­сов и раз­меров

Те­перь спря­чем информа­цию о вер­сии.

Шиф­рование ресур­сов исполня­емо­го фай­ла
Шиф­рование ресур­сов исполня­емо­го фай­ла

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

По­иск области, содер­жащей стро­ки, и ее шиф­рование
По­иск области, содер­жащей стро­ки, и ее шиф­рование

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

Те­перь самое вре­мя про­верить, что мы все спря­тали, и наш mimikatz боль­ше не вызыва­ет подоз­рений.

Все вре­донос­ное содер­жимое успешно скры­то
Все вре­донос­ное содер­жимое успешно скры­то

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

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

Наш xor_stub.asm будет сох­ранять началь­ное сос­тояние и добав­лять пра­ва на запись в зашиф­рован­ные сек­ции.

Из­менение permissions зашиф­рован­ных сек­ций
Из­менение permissions зашиф­рован­ных сек­ций

Здесь и далее не забудь изме­нить адре­са WinAPI-фун­кций на свои зна­чения. Теперь мы скор­ректи­руем точ­ку вхо­да, так как в нее чуть поз­же будет встав­лен jump на дан­ный код.

Вос­ста­нов­ление инс­трук­ций точ­ки вхо­да
Вос­ста­нов­ление инс­трук­ций точ­ки вхо­да

Зап­росим у поль­зовате­ля ключ для рас­шифров­ки и выпол­ним де-xor всех зашиф­рован­ных областей.

Зап­рос клю­ча и рас­шифров­ка им зак­сорен­ных областей
Зап­рос клю­ча и рас­шифров­ка им зак­сорен­ных областей

На­конец мы вос­ста­нав­лива­ем началь­ное сос­тояние, кор­ректи­руем стек и переме­щаем­ся в entry point.

Воз­врат на точ­ку вхо­да
Воз­врат на точ­ку вхо­да

Са­мое вре­мя добавить пус­тую сек­цию r-x в mimikatz, куда мы раз­местим наш xor_stub.

До­бав­ление сек­ции, куда будет встав­лен код рас­шифров­ки
До­бав­ление сек­ции, куда будет встав­лен код рас­шифров­ки

Те­перь ском­пилиру­ем дан­ный ассем­блер­ный код и вста­вим его в толь­ко что соз­данную сек­цию.

Ком­пиляция и встав­ка кода для рас­шифров­ки
Ком­пиляция и встав­ка кода для рас­шифров­ки

В кон­це не забудем из entry point сде­лать jump на наш код.

Пе­реда­ча управле­ния на код рас­шифров­ки
Пе­реда­ча управле­ния на код рас­шифров­ки

Го­тово. Запус­каем и вво­дим ключ, которым мы шиф­ровали, — в моем слу­чае это сим­вол w (0x77).

Вы­пол­нение вре­донос­ного кода в обход анти­виру­са
Вы­пол­нение вре­донос­ного кода в обход анти­виру­са

Вот и все. Нем­ного авто­мати­зиро­вав дан­ный про­цесс, поп­робу­ем запус­тить Meterpreter.

Ав­томати­чес­кое шиф­рование опи­сан­ным спо­собом
Ав­томати­чес­кое шиф­рование опи­сан­ным спо­собом

За­пус­каем и вво­дим ключ w.

За­пуск вре­донос­ного кода
За­пуск вре­донос­ного кода

И получа­ем тот же эффект.

За­пуск вре­донос­ного кода в обход анти­виру­са
За­пуск вре­донос­ного кода в обход анти­виру­са

Vuln inject (spawn)

Мне хорошо запом­нился один дав­ний слу­чай. Я никак не мог открыть сес­сию Meterpreter на victim из‑за анти­виру­са, и вмес­то это­го мне каж­дый раз при­ходи­лось заново экс­плу­ати­ровать ста­рую доб­рую MS08-067, запус­кая Meterpreter сра­зу в памяти. Анти­вирус почему‑то не мог помешать это­му.

Ду­маю, анти­вирус глав­ным обра­зом заточен на отлов прог­рамм (на HDD) и шелл‑кодов (по сети) с извес­тны­ми сиг­натура­ми или на экс­плу­ата­цию популяр­ных уяз­вимос­тей. Но что, если уяз­вимость еще неиз­вес­тна для анти­виру­са?

В этом слу­чае исполь­зует­ся дос­таточ­но необыч­ная тех­ника, которая стро­ится на прин­ципе внед­рения уяз­вимос­ти (buffer overflow) и, тем самым, неоче­вид­ном исполне­нии про­изволь­ного кода. По сути, это еще один вари­ант реф­лектив­ного исполне­ния кода, то есть ког­да код при­сутс­тву­ет исклю­читель­но в RAM, минуя HDD. Но в нашем слу­чае мы еще и скры­ваем точ­ку вхо­да во вре­донос­ный код.

Ан­тивирус, как и любое дру­гое ПО, вряд ли спо­собен опре­делить ста­тичес­ким ана­лиза­тором, что в прог­рамме содер­жится уяз­вимость и будет исполнен про­изволь­ный код. Машины пока пло­хо справ­ляют­ся с этим, и не думаю, что ситу­ация силь­но изме­нит­ся в бли­жай­шем будущем. Но смо­жет ли анти­вирус уви­деть про­цесс в динами­ке и успеть сре­аги­ровать?

Что­бы про­верить это, нам нуж­но написать и запус­тить прос­тень­кий сетевой сер­вис, содер­жащий при­думан­ную нами 0-day-уяз­вимость (buffer overflow на сте­ке) и про­экс­плу­ати­ровать ее. Что­бы все работа­ло еще и в новых вер­сиях Windows, при­дет­ся обой­ти DEP. Но это не проб­лема, если мы можем добавить нуж­ные нам ROP-gadgets в прог­рамму.

Не будем глу­боко вда­вать­ся в детали buffer overflow и ROP-chains, так как это выходит за рам­ки дан­ной статьи. Вмес­то это­го возь­мем го­товое решение. Ком­пилиру­ем сер­вис обя­затель­но без под­дер­жки ASLR (и мож­но без DEP):

Наш сетевой сер­вис будет работать в режимах listen и reverse connect. А все дан­ные будут переда­вать­ся в зашиф­рован­ном виде, что­бы не спа­лить­ся на сиг­натур­ном ана­лиза­торе. И это очень важ­но, пос­коль­ку некото­рые анти­виру­сы нас­толь­ко «не любят» Meterpreter, что даже прос­тая его отправ­ка в любой откры­тый порт спро­воци­рует немину­емый алерт и пос­леду­ющий бан IP-адре­са ата­кующе­го.

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

За­пуск сер­виса с заложен­ной нами buffer overflow
За­пуск сер­виса с заложен­ной нами buffer overflow

Наш уяз­вимый сер­вис успешно запущен и ждет вхо­дящих дан­ных. Соз­даем payload и запус­каем экс­пло­ит с ним.

Экс­плу­ата­ция нашего buffer overflow
Экс­плу­ата­ция нашего buffer overflow

В ито­ге уяз­вимый сер­вис при­нима­ет наши дан­ные и в резуль­тате заложен­ной ошиб­ки при работе с памятью неп­роиз­воль­но запус­кает код payload.

Пе­репол­нение буфера в дей­ствии
Пе­репол­нение буфера в дей­ствии

Эта полез­ная наг­рузка откры­вает нам сес­сию Meterpreter.

Вы­пол­нение вре­донос­ного кода в обход анти­виру­са
Вы­пол­нение вре­донос­ного кода в обход анти­виру­са

И все это безоб­разие про­исхо­дит при работа­ющем анти­виру­се. Одна­ко было замече­но, что при исполь­зовании некото­рых анти­виру­сов все еще сра­баты­вает защита. Давай порас­сужда­ем, почему. Мы вро­де бы смог­ли внед­рить код край­не неожи­дан­ным спо­собом — через buffer overflow. И в то же самое вре­мя по сети мы не переда­вали код в откры­том виде, так что сиг­натур­ные движ­ки не сра­бота­ли бы. Но перед непос­редс­твен­ным исполне­нием мы получа­ем в памяти тот самый машин­ный код. И тут, по‑видимо­му, анти­вирус и ловит нас, узна­вая до боли зна­комый Meterpreter.

Ан­тивирус не доверя­ет exe-фай­лу, ска­чан­ному неиз­вес­тно отку­да и запущен­ному пер­вый раз. Он эму­лиру­ет выпол­нение кода и дос­таточ­но глу­боко ана­лизи­рует его, воз­можно даже на каж­дой инс­трук­ции. За это при­ходит­ся пла­тить про­изво­дитель­ностью, и анти­вирус не может поз­волить себе делать так для всех про­цес­сов. Поэто­му про­цес­сы, уже про­шед­шие про­вер­ку на эта­пе запус­ка (нап­ример, сис­темные ком­понен­ты или прог­раммы с извес­тной кон­троль­ной сум­мой), работа­ют под мень­шим над­зором. Имен­но в них мы и внед­рим нашу уяз­вимость.

Vuln inject (attach)

Са­мый прос­той и удоб­ный спо­соб выпол­нить код в чужом адресном прос­транс­тве (про­цес­се) — инжект биб­лиоте­ки. Бла­го DLL мало чем отли­чает­ся от EXE и мы можем переком­пилиро­вать наш уяз­вимый сер­вис в форм‑фак­тор биб­лиоте­ки, прос­то изме­нив main() на DllMain():

Для мак­сималь­ной перено­симос­ти я исполь­зую 32-бит­ные прог­раммы, поэто­му внед­рять уяз­вимость нам при­дет­ся в 32-раз­рядные про­цес­сы. Мож­но взять любой уже запущен­ный или запус­тить самому. На 64-бит­ной Windows мы всег­да можем най­ти 32-бит­ные сис­темные прог­раммы в c:\windows\syswow64.

За­пуск 32-бит­ного про­цес­са
За­пуск 32-бит­ного про­цес­са

Те­перь в тот или иной 32-бит­ный про­цесс мы можем внед­рить уяз­вимость, прос­то заин­жектив туда нашу биб­лиоте­ку.

Ин­жект биб­лиоте­ки в толь­ко что запущен­ный 32-бит­ный сис­темный про­цесс
Ин­жект биб­лиоте­ки в толь­ко что запущен­ный 32-бит­ный сис­темный про­цесс

На­ша DLL без ASLR успешно заг­ружена по стан­дар­тно­му адре­су.

Уяз­вимая DLL заг­ружена
Уяз­вимая DLL заг­ружена

И теперь целевой про­цесс с занесен­ным buffer overflow готов получать дан­ные по сети.

Уяз­вимый код начал работать в кон­тек­сте легитим­ного про­цес­са
Уяз­вимый код начал работать в кон­тек­сте легитим­ного про­цес­са

Пос­коль­ку наш уяз­вимый модуль заг­рузил­ся по адре­су 0x10000000 (это дефол­тный адрес для не ASLR-биб­лиотек), нуж­но слег­ка скор­ректи­ровать код экс­пло­ита.

Не­боль­шая кор­ректи­ров­ка кода экс­пло­ита
Не­боль­шая кор­ректи­ров­ка кода экс­пло­ита

Вре­мя запус­тить сам экс­пло­ит.

За­пуск экс­пло­ита
За­пуск экс­пло­ита

В кон­тек­сте легитим­ного про­цес­са про­исхо­дит overflow.

Пе­репол­нение буфера в легитим­ном про­цес­се в дей­ствии
Пе­репол­нение буфера в легитим­ном про­цес­се в дей­ствии

И мы исполня­ем «вре­донос­ный» код в обход анти­виру­са.

Вы­пол­нение вре­донос­ного кода в обход анти­виру­са
Вы­пол­нение вре­донос­ного кода в обход анти­виру­са

Выводы

Мы исполь­зовали эффект «неожи­дан­ного» исполне­ния кода в памяти через 0-day-уяз­вимость, анти­вирус не смог ее спрог­нозиро­вать и заб­локиро­вать угро­зу. Заг­рузка DLL в чужой про­цесс — трюк дос­таточ­но извес­тный, и мы исполь­зовали его исклю­читель­но для удобс­тва: нам поч­ти не приш­лось ничего менять.

На самом деле мы мог­ли исполь­зовать еще более хит­рый спо­соб — прос­то под­менить клю­чевые инс­трук­ции в той или иной точ­ке памяти про­цес­са, где про­исхо­дит обра­бот­ка поль­зователь­ско­го вво­да, и внед­рить тем самым туда уяз­вимость (как бы сде­лать анти­патч). А положив пару‑трой­ку удоб­ных ROP-гад­жетов в code caves, сде­лать ее еще и при­год­ной к экс­плу­ата­ции. Но пока что это­го даже не тре­бует­ся.

Тех­ника сок­рытия выпол­нения кода через buffer overflow не нова, хоть и дос­таточ­но мало­извес­тна. В дан­ном при­мере был исполь­зован самый три­виаль­ный при­мер buffer overflow на сте­ке, и он при­нес нам успех. Но сущес­тву­ют куда более хит­рые ошиб­ки работы с памятью, при­водя­щие к RCE (скры­тому исполне­нию): use after free, double free, overflow in heap, format strings и так далее. Это откры­вает прак­тичес­ки неис­черпа­емый потен­циал для при­емов обхо­да анти­вирус­ных прог­рамм.

Дима (Kozhuh)

Эксперт в кибербезопасности. Работал в ведущих компаниях занимающихся аналитикой компьютерных угроз. Анонсы новых статей в Телеграме.

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

  1. John

    То есть все крипторы работают подобным методом м переполнением буфера ? Если так почему они очень быстро паляться после компеляции и детектятся , не переписывать же все время софт . Есть ли какие либо решение для скрипт-кидди ? Или может совет куда копать . Peace !

    Ответить