Обход антивирусов на практике. Разбираем на пальцах сигнатурный движок YARA

1 неделя назад 6

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

YARA — это популяр­ный инс­тру­мент для поис­ка и клас­сифика­ции мал­вари. Он работа­ет на осно­ве спе­циаль­ных пра­вил, которые дви­жок может при­менять к про­цес­сам и фай­лам. YARA соз­дал Вик­тор Аль­варес (Victor Alvarez) во вре­мя работы в VirusTotal. Сегод­ня инс­тру­мент слу­жит стан­дартом де‑фак­то для опи­сания сиг­натур вре­донос­ных прог­рамм.

По­нима­ние его внут­ренней работы дает пред­став­ление о том, как устро­ены анти­вирус­ные сиг­натуры и почему они выг­лядят имен­но так. Дви­жок лег­ко рас­ширя­ется за счет поль­зователь­ских модулей на C, что поз­воля­ет добав­лять про­изволь­ную логику ана­лиза. На при­мере YARA лег­ко прос­ледить эво­люцию анти­вирус­ных тех­нологий: от прос­тей­ших скан‑строк эпо­хи DOS до сов­ремен­ных сос­тавных пра­вил с вло­жен­ными усло­виями и дан­ными от натив­ных модулей. В этой гла­ве рас­смат­рива­ется YARA 4.5.8 — акту­аль­ная вер­сия на момент под­готов­ки тек­ста.

Книга «Обход антивирусов на практике»

Это пер­вая гла­ва из кни­ги «Об­ход анти­виру­сов на прак­тике».

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

Правила

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

rule MD5_API {

meta:

author = "_pusher_"

description = "Looks for MD5 API"

date = "2016-07"

strings:

$advapi32 = "advapi32.dll" wide ascii nocase

$cryptdll = "cryptdll.dll" wide ascii nocase

$MD5Init = "MD5Init" wide ascii

$MD5Update = "MD5Update" wide ascii

$MD5Final = "MD5Final" wide ascii

condition:

($advapi32 or $cryptdll) and ($MD5Init and $MD5Update and $MD5Final)

}

Нап­ример, это пра­вило детек­тиру­ет при­мене­ние фун­кций WinAPI, спе­циали­зиру­ющих­ся на рас­чете MD5-хеша. Оно ищет строч­ки с модифи­като­рами wide ascii nocase — вари­ации с любым регис­тром, в одно­бай­товом (ASCII) или двух­бай­товом (wide) бинар­ном пред­став­лении, пос­ле чего про­веря­ет истинность усло­вия (condition) — наличие хотя бы одной из двух DLL и всех ука­зан­ных API.

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

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

Шестнадцатеричные строки

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

rule RawBytes

{

strings:

$hex = { E2 34 A1 C8 23 FB }

condition:

$hex

}

Че­тыре спе­циаль­ные конс­трук­ции — wildcard, отри­цание, перехо­ды и аль­тер­нативы — прев­раща­ют при­митив­ный побай­товый поиск в пол­ноцен­ный язык опи­сания бинар­ных пат­тернов. Без них опи­сание сиг­натур байт‑кода было бы прак­тичес­ки невоз­можным, потому что даже минималь­ные раз­личия в сбор­ке (дру­гой ком­пилятор, дру­гая вер­сия, дру­гие фла­ги опти­миза­ции) при­водят к изме­нению кон­крет­ных бай­тов.

Wildcard (подстановочные символы)

Сим­вол ? заменя­ет неиз­вес­тный полубайт (nibble). Wildcard работа­ет имен­но на уров­не полубай­тов — это прин­ципи­аль­но, пос­коль­ку поз­воля­ет зафик­сировать стар­шую или млад­шую полови­ну бай­та, оста­вив дру­гую про­изволь­ной. Такая гра­нуляр­ность нуж­на, нап­ример, при опи­сании опко­дов x86, где стар­ший полубайт кодиру­ет опе­рацию, а млад­ший — регистр.

$hex = { E2 34 ?? C8 A? FB }

Здесь:

  • ?? — любой байт целиком (от 00 до FF);
  • A? — стар­ший полубайт равен A, млад­ший — любой (от A0 до AF).

Инс­трук­ции x86 час­то содер­жат перемен­ные поля (ModR/M, SIB, displacement), и wildcard поз­воля­ет абс­тра­гиро­вать­ся от кон­крет­ных регис­тров или сме­щений. Нап­ример, инс­трук­ция mov 32-бит­ного регис­тра кодиру­ется бай­тами B8 — BF в зависи­мос­ти от целево­го регис­тра, а call — бай­том E8 с четырь­мя бай­тами отно­ситель­ного адре­са. Запись B? здесь не под­ходит, пос­коль­ку она охва­тыва­ет весь диапа­зон B0 — BF. Поэто­му опко­ды mov нуж­но перечис­лить явно:

( B8 | B9 | BA | BB | BC | BD | BE | BF ) ?? ?? ?? ??

Для call дос­таточ­но шаб­лона E8 ?? ?? ?? ??, где четыре бай­та wildcard заменя­ют отно­ситель­ный адрес.

NOT (отрицание)

Опе­ратор ~ (тиль­да) перед бай­том зна­чит «любое зна­чение, кро­ме ука­зан­ного». Если wildcard — это «мне все рав­но, какой байт», то отри­цание — это «мне все рав­но, какой байт, лишь бы не вот этот». Ком­бинация wildcard и отри­цания поз­воля­ет исклю­чать кон­крет­ные нежела­тель­ные вари­анты без необ­ходимос­ти перечис­лять все допус­тимые аль­тер­нативы.

$hex = { F4 23 ~00 62 B4 }

$hex2 = { F4 23 ~?0 62 B4 }

  • ~00 — любой байт, кро­ме нулево­го;
  • ~0? — любой байт, у которо­го стар­ший полубайт не равен 0.

По­лез­но для исклю­чения кон­крет­ных вари­антов инс­трук­ций или дан­ных. Нап­ример, мож­но опи­сать jmp short с ненуле­вым сме­щени­ем (EB ~00) или пос­ледова­тель­ность бай­тов, где опре­делен­ное поле не дол­жно быть пус­тым. Отри­цание так­же ком­биниру­ется с полубай­товым wildcard: запись ~?0 зна­чит «млад­ший полубайт не равен нулю», что пок­рыва­ет 240 из 256 воз­можных зна­чений бай­та вмес­то 255.

Jumps (переходы)

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

$hex = { F4 23 [4-6] 62 B4 }

Это зна­чит: меж­ду F4 23 и 62 B4 может быть от 4 до 6 про­изволь­ных бай­тов. Допус­тимые фор­мы:

Син­таксисРас­шифров­ка
[4-6]От 4 до 6 байт
[10-]От 10 байт до бес­конеч­ности
[-]От 0 байт до бес­конеч­ности

Пе­рехо­ды кри­тичес­ки важ­ны в нес­коль­ких типич­ных сце­нари­ях.

Во‑пер­вых, ком­пилятор может встав­лять вырав­нивание (padding) и инс­трук­ции NOP меж­ду фун­кци­ями, и их количес­тво неп­ред­ска­зуемо.

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

В‑треть­их, дан­ные в струк­турах фай­ловых фор­матов могут иметь поля перемен­ной дли­ны (стро­ки, мас­сивы), и переход поз­воля­ет «переп­рыгнуть» их, при­вязав­шись к мар­керам начала и кон­ца. Неог­раничен­ный переход [-] — это, по сути, утвер­жде­ние «эти два пат­терна встре­чают­ся в фай­ле в ука­зан­ном поряд­ке на про­изволь­ном рас­сто­янии».

Alternatives (альтернативы)

Конс­трук­ция ( ... | ... ) зада­ет нес­коль­ко допус­тимых вари­антов для фраг­мента. Син­таксис заимс­тво­ван из регуляр­ных выраже­ний: вари­анты перечис­ляют­ся через | внут­ри круг­лых ско­бок.

$hex = { F4 23 ( 62 B4 | 56 | 45 ?? 67 ) 45 }

Источник