Вирусы в UNIX, или Гибель Титаника II

       

Заражение посредством расширения кодовой секции файла


Наибольшую скрытность вирусу обеспечивает внедрение в кодововую секцию заражаемого файла, находящуюся глубоко в середине последнего. Тело вируса, сливаясь с исходным машинным кодом, виртуально становится совершенно неотличимым от "нормальной" программы, и обнаружить такую заразу можно лишь анализом ее алгоритма (см. так же "Основные признаки вирусов").

Безболезненное расширение кодовой секции возможно лишь в ELF- и COFF-файлах (под "безболезненностью" здесь понимается отсутствие необходимости в перекомпиляции файла-жертвы), и достигается оно за счет того замечательного обстоятельства, что стартовые виртуальные адреса сегментов/секций отделены от их физических смещений, отсчитываемых от начала файла.

Алгоритм заражения ELF-файла в общем виде выглядит так (внедрение в COFF-файлы осуществляется аналогичным образом):

q       вирус открывает файл и, считав его заголовок, убеждается, что это действительно ELF;

q       заголовок таблицы секций (Section Header Table) перемещается вниз на величину, равную длине тела вируса. Для этого вирус увеличивает содержимое поля e_shoff, оккупирующего 20h – 23h байты ELF-заголовка, (примечание: заголовок таблицы секций, равно как и сами секции, имеет значение только для компоновочных файлов, загрузчик исполняемых файлов их игнорирует, независимо от того присутствуют они в файле или нет);

q       просматривая Program Header Table, вирус находит сегмент, наиболее предпочтительный для заражения (т. е. тот сегмент, в который указывает точка входа);

q       длина найденного сегмента увеличивается на величину, равную размеру тела вируса. Это осуществляется путем синхронной коррекции полей p_filez и p_memz;

q       все остальные сегменты смещаются вниз, при этом поле p_offset каждого из них увеличивается на длину тела вируса;

q       анализируя заголовок таблицы секций (если он только присутствует в файле), вирус находит секцию, наиболее предпочтительную для заражения (как правило заражается секция, находящаяся в сегменте последней: это избавляет вируса от необходимости перемещения всех остальных секций вниз);


q       размер заражаемой секции ( поле sh_size) увеличивается на величину, равную размеру тела вируса;

q       все хвостовые секции сегмента смещаются вниз, при этом поле sh_offset каждой из них увеличивается на длину тела вируса (если вирус внедряется в последнюю секцию сегмента, этого делать не нужно);

q       вирус дописывает себя к концу заражаемого сегмента, физически смещая содержимое всей остальной части файла вниз;



q       для перехвата управления вирус корректирует точку входа в файл (e_entry) либо же внедряет в истинную точку входа jmp на свое тело (впрочем, методика перехвата управления тема отдельного большого разговора).

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

Большинство файлов включает в себя более одной кодовой секции, и располагаются эти секции приблизительно так:

 .init        содержит инициализационный код

 .plt         содержит таблицу связки подпрограмм

 .text        содержит основной код программы

 .fini        содержит термирующий код программы

Листинг 10 схема расположения кодовых секций типичного файла

Присутствие секции .finit

делает секцию .text

не последней секцией кодового сегмента файла, как чаще всего и происходит. Таким образом, в зависимости от стратегии распределения секций по сегментам, последней секцией файла обычно является либо секция .finit, либо .rodata.

Секция .finit

в большинстве своем это такая крохотная секция, заражение которой трудно оставить незамеченным. Код, расположенный в секции .finit и непосредственно перехватывающий на себя нить выполнения программой, выглядит несколько странно, если не сказать – подозрительно (обычно управление на .finit передается косвенным образом как аргумент функции atexit). Вторжение будет еще заметнее, если последней секцией в заражаемом сегменте окажется секция .rodata (машинный код при нормальном развитии событий в данные никогда не попадает). Не остается незамеченным и вторжение в конец первой секции кодового сегмента (в последнюю секцию сегмента, предшествующему кодовому сегменту), поскольку кодовый сегмент практически всегда начинается с секции .init, вызываемой из глубины стартового кода и по обыкновению содержащей пару-тройку машинных команд. Вирусу здесь будет просто негде затеряться и его присутствие сразу же становится заметным!



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

Последний случай стоит рассмотреть особо. Системному загрузчику, ничего не знающему о существовании секций, степень их выравнивания по барабану (простите, я хотел сказать "…она для него некритична"). Тем не менее, во всех нормальных исполняемых файлах секции тщательно выровнены на величину, указанную в поле sh_addralign. При заражении файла вирусом последний далеко не всегда оказывается так аккуратен, и некоторые секции могут неожиданно для себя очутиться по некратным адресам. Работоспособности программы это не нарушит, но вот факт вторжения вируса сразу же демаскирует.

Сегменты выравнивать тоже необязательно (при необходимости системный загрузчик сделает это сам), однако программистский этикет предписывает выравнивать секции, даже если поле p_align равно нулю (т. е. выравнивания не требуется). Все нормальные линкеры выравнивают сегменты по крайней мере на величину кратную 32 байтам, хотя это происходит и не всегда. Тем не менее, если сегменты, следующие за сегментом кода, выровнены на меньшую величину – к такому файлу следует присмотреться повнимательнее.

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



Выравнивание функций внутри секций – это вообще вещь (в смысле: вещдок – вещественное доказательство). Кратность выравнивания функций нигде и никак не декларируется, и всякий программист склонен выравнивать функции по своему. Одни используют выравнивание на адреса кратные 04h, другие – 08h, 10h или даже 20h! Определить степень выравнивания без качественного дизассемблера практически невозможно. Требуется выписать стартовые адреса всех функций и найти наибольший делитель, на который все они делятся без остатка. Дописывая себя в конец кодового сегмента, вирус наверняка ошибется с выравниванием адреса пролога функции (если он вообще позаботиться о создании функции в этом месте!), и он окажется отличным от степени выравнивания, принятой всеми остальными функциями (попутно заметим, что определять степень выравнивания при помощи дизассемблера IDA PRO – плохая идея, т. к. она определяет ее неправильно, закладываясь на наименьшее

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

Классическим примером вируса, внедряющегося в файл путем расширения кодового сегмента, является вирус Linux.Vit.4096. Любопытно, что различные авторы по разному описывают стратегии, используемые вирусом для заражения. Так, Евгений Касперский почему-то считает, что вирус Vit записывается в начало кодовой секции заражаемого файла (http://www.viruslist.com/viruslist.html?id=3276) в то время как он размещает свое тело в конце кодового сегмента

файла (http://www.nai.com/common/media/vil/pdf/mvanvoers_VB_conf 202000.pdf). Ниже приведен фрагмент ELF-файла, зараженного вирусом Vit.



Рисунок 6 фрагмент файла, зараженного вирусом Lin/Vit. Поля, модифицированные вирусом, выделены траурной рамкой

Многие вирусы (и в частности вирус Lin/Obsidan) выдают себя тем, что при внедрении в середину файла "забывают" модифицировать заголовок таблицы секций (либо же модифицируют его некорректно). Как уже отмечалось выше, в процессе загрузки исполняемых файлов в память системный загрузчик считывает информацию о сегментах и проецирует их содержимое целиком. Внутренняя структура сегментов его совершенно не интересует. Даже если заголовок таблицы секций отсутствует или заполнен некорректно, запущенная на выполнение программа будет исправно работать. Однако несмотря на это, в подавляющем большинстве исполняемых файлов заголовок таблицы секций все-таки присутствует, и попытка его удаления оканчивается весьма плачевно – популярный отладчик gdb и ряд других утилит для работы с ELF-файлами отказываются признать "кастрированный" файл своим. При заражении исполняемого файла вирусом, некорректно обращающимся с заголовком таблицы секций, поведение отладчика становится непредсказуемым, демаскируя тем самым факт вирусного вторжения.



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

q       поле e_shoff указывает "мимо" истинного заголовка таблицы секций (так себя ведет вирус Lin/Obsidan) либо имеет нулевое значение при непустом заголовке таблицы секций (так себя ведет вирус Linux.Garnelis);

q       поле e_shoff имеет ненулевое значение, но ни одного заголовка таблицы секций в файле нет;

q       заголовок таблицы секций содержится не в конце файла, этих заголовков несколько или заголовок таблицы секций попадает в границы владения одного из сегментов;

q       сумма длин всех секций одного сегмента не соответствует его полной длине;

q       программный код расположен в области, не принадлежащей никакой секции;

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


Содержание раздела