Как обходить антивирус при пентесте

Как обходить антивирус при пентесте

Hack proof


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

предупреждение

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

Во­обще, задача обхо­да анти­виру­са может воз­никнуть в двух слу­чаях:

  • при ата­ке. Тут полез­ную наг­рузку запус­кает либо уяз­вимое при­ложе­ние, либо, что чаще, поль­зователь (соци­аль­ная инже­нерия). Глав­ным обра­зом это будет некое средс­тво зак­репле­ния и обес­печения пос­тоян­ного при­сутс­твия. Самое важ­ное — не спа­лить­ся;
  • при пос­тэкс­плу­ата­ции. В этом слу­чае мы сами запус­каем прог­рамму на ском­про­мети­рован­ной сис­теме. Это может быть сниф­фер, средс­тво повыше­ния при­виле­гий или прос­то какой‑то хакер­ский софт, исполь­зуемый для прод­вижения по сети. И при этом для нас важ­нее запус­тить прог­рамму, даже если это получи­лось не с пер­вой попыт­ки и анти­вирус выкинул нес­коль­ко алер­тов.

В пер­вом слу­чае дос­таточ­но лишь при­менить извес­тную тех­нику — полимор­физм. Изме­няем код, не меняя его фун­кци­ональ­ность. Хорошая так­тика — написать код самому. Нап­ример, с помощью двад­цати строк кода на VBS мож­но реали­зовать прос­той reverse shell и успешно обой­ти любой анти­вирус. Нас же боль­ше будет инте­ресо­вать вто­рой слу­чай, и имен­но это и будет темой дан­ной статьи.

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

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

По боль­шому сче­ту мы име­ем дело с дву­мя механиз­мами защиты:

  • сиг­натур­ным;
  • эв­ристи­чес­ким (поведен­ческим).

При сиг­натур­ном ана­лизе анти­вирус учи­тыва­ет мно­жес­тво фак­торов, в час­тнос­ти боль­шое вли­яние на резуль­тат ока­зыва­ет ком­пилятор. Вот дос­таточ­но забав­ные резуль­таты VirusTotal для безобид­ного helloworld-при­ложе­ния, написан­ного на С:

  • i686-w64-mingw32-gcc — 11/68 детектов;
  • msvc — 2/64 детек­та;
  • win-gcc — 0 детек­тов.

Что же каса­ется ана­лиза поведе­ния прог­раммы, тут нуж­но понимать, что, даже если тебе уда­лось обой­ти сиг­натуры, ты все еще можешь спа­лить­ся, пос­коль­ку вся­кий migrate PID или sekurlsa::logonPasswords может быть перех­вачен по при­чине исполь­зования харак­терных сочета­ний WinAPI-фун­кций, которые анти­виру­сы очень вни­матель­но монито­рят.

Я пред­лагаю сос­редото­чить­ся имен­но на сиг­натур­ном движ­ке, обхо­да которо­го для боль­шинс­тва слу­чаев дос­таточ­но. В статье не будет пря­мых упо­мина­ний кон­крет­ных наз­ваний анти­виру­сов, что­бы не соз­давать никому рек­ламы или анти­рек­ламы. В то же вре­мя мы не ста­нем «затачи­вать­ся» под кон­крет­ный анти­вирус. Резуль­таты будем про­верять имен­но на работа­ющем анти­виру­се, при этом поп­робу­ем исполь­зовать некий уни­вер­саль­ный спо­соб, что­бы каж­дый раз не при­думы­вать все новые методы обхо­да. В каж­дом слу­чае целью будет тай­ком про­тащить на ском­про­мети­рован­ную машину Meterpreter, который поз­волит нам исполнить в памяти что угод­но, запус­тить весь име­ющий­ся в нашем рас­поряже­нии хакер­ский арсе­нал.

 

Юридическая информация

Луч­ший бой — это тот, которо­го уда­лось избе­жать. Поэто­му в борь­бе с анти­виру­сами час­то исполь­зуют­ся легаль­ные средс­тва. Да, они не могут пре­дос­тавить мно­гие «прод­винутые шту­ки», но необ­ходимый минимум в виде reverse shell при persistence и lateral movement, а так­же встро­енный прок­си‑сер­вер и гиб­кую сис­тему редирек­та тра­фика при pivoting они реали­зовать могут. И это замеча­тель­ные, всем извес­тные ути­литы — nc.exe, ncat.exe, socat.exe, plink.exe. При­меры их исполь­зования были опи­саны в моих прош­лых стать­ях. Бле­ки же и вов­се порою исполь­зуют обыч­ные средс­тва облачно­го уда­лен­ного адми­нис­три­рова­ния вро­де RMS.

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

 

Ввод шелл-кода

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

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

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

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

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

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

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

 

Кодовые пещеры

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

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

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

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

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

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

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

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

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

 

Склеп

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

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

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

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

PE->NT headers->Optional header->DllCharacteristics |=0x40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

Вульн впрыскивает (порождает)

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

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

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

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

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

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

cl.exe /c vuln_rop.c

link.exe /out:vuln_rop.exe vuln_rop.obj /nxcompat:no /fixed

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

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

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

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

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

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

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

 

Вульн впрыскивает (присоединяет)

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

cl.exe /c vuln_rop.c

link.exe vuln_rop.obj /out:vuln_rop.dll /dll /nxcompat:no /fixed

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

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

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

И теперь целевой про­цесс с занесен­ным 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 и так далее. Это откры­вает прак­тичес­ки неис­черпа­емый потен­циал для при­емов обхо­да анти­вирус­ных прог­рамм.




Report Page