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

       

На правах врезки "основные признаки вирусов"


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

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

Ведь чтобы заразить жертву, вирус прежде должен ее найти, отобрав среди всех кандидатов только файлы "своего" типа. Для определенности возьмем ELF. Тогда вирус будет вынужден считать его заголовок и сравнить четыре первых байта со строкой "¦FELF", которой соответствует ASCII-последовательность 7F 45 4C 46. Конечно, если тело вируса зашифровано, вирус использует хеш-сравнение или же другие хитрые приемы программирования, строки "ELF" в теле зараженного файла не окажется, но более чем в половине всех существующих UNIX-вирусов она все-таки есть, и этот прием, несмотря на свою изумительную простоту, очень неплохо работает.

Загрузите исследуемый файл в любой HEX-редактор и попробуйте отыскать строку "¦ELF". В зараженном файле таких строк будет две – она непосредственно в заголовке, другая – в кодовой секции или секции данных. Только не используйте дизассемблер! Очень многие вирусы преобразуют строку "¦FELF" в 32-разрядную целочисленную константу 464С457Fh, которая маскирует присутствие вируса, но при переключении в режим дампа, сразу же "проявляется" на экране. Ниже приведен внешний вид файла, зараженного вирусом VirTool.Linux.Mmap.443, который использует именно такую методику:




Рисунок 9 0x001 фрагмент файла, зараженного вирусом VirTool.Linux.Mmap.443. В HEX-дампе легко обнаруживается строка "ELF", используемая вирусом для поиска жертв "своего" типа

Вирус Linux.Winter.343 (также известный под именем Lotek) по этой методике обнаружить не удается, поскольку он использует специальное математическое преобразование, зашифровывая строку "¦ELF" на лету:

.text:08048473             mov    eax, 0B9B3BA81h      ; -"ELF" (минус "ELF")

.text:08048478             add    eax, [ebx]           ; первые четыре байта жертвы

.text:0804847A             jnz    short loc_804846E    ; à это

не ELF

Листинг 13 фрагмент вируса Lotek, тщательно скрывающего свой интерес к ELF-файлам



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

Как вариант, вирус может дополнять эталонную строку "¦ELF" до единицы, и тогда в его теле будет присутствовать последовательность 80 BA B3 B9. Реже встречаются циклические сдвиги на одну, две, три… и семь позиций в различные стороны, неполные проверки (т. е. проверки на совпадение двух или трех байт) и некоторые другие операции – всех не перечислишь!

Более уязвимым с точки зрения скрытности является механизм реализации системных вызовов. Вирус не может позволить себе тащить за собой всю библиотеку LIBC, прилинкованную к нему статической компоновкой, поскольку существование подобного монстра трудно оставить незаметным. Существует несколько способов решения этой проблемы и наиболее популярный из них сводится к использованию native?API операционной системы. Поскольку последний является прерогативой особенностей реализации данной конкретной системы, создатели UNIX де-факто отказались от многочисленных попыток его стандартизации. В частности, в System V (и ее многочисленных клонах) обращение к системным функциям происходит через дальний call по адресу 0007:00000000, а в Linux это осуществляется через служебное прерывание INT 80h (перечень номеров системных команд можно найти в файле /usr/include/asm/unistd.h). Таким образом, использование native?API существенно ограничивает ареал обитания вируса, делая его непереносимым.



Честные программы в большинстве своем практически никогда не работают через native?API (хотя утилиты из комплекта поставки Free BSD 4.5 ведут себя именно так), поэтому наличие большого количества машинных команд INT 80h/CALL 0007:0000000 (CD 80/9A 00 00 00 00 07 00) с высокой степенью вероятности свидетельствует о наличии вируса. Для предотвращения ложных срабатываний (т. е. обнаружения вируса там, где и следов его нет), вы должны не только обнаружить обращения к native?API, но и проанализировать последовательность их вызовов. Для вирусов характерна следующая цепочка системных команд: sys_open, sys_lseek, old_mmap/sys_munmap, sys_write, sys_close, sys_exit. Реже используются вызовы exec и fork. Их, в частности, использует вирус STAOG.4744. Вирусы VirTool.Linux.Mmap.443, VirTool.Linux.Elfwrsec.a, PolyEngine.Linux.LIME.poly, Linux.Winter.343 и ряд других обходятся без этого.

Ниже приведен фрагмент файла, зараженного вирусом VirTool.Linux.Mmap.443. Наличие незамаскированных вызовов INT 80h с легкостью разоблачает агрессивную природу программного кода, указывая на склонность последнего к саморазмножению:



Рисунок 10 0x003 фрагмент файла, зараженного вирусом VirTool.Linux.Mmap.443, демаскирующим свое присутствие прямым обращением к native?API операционной системы

А вот так для сравнения выглядят системные вызовы "честной" программы – утилиты cat из комплекта поставки Free BSD 4.5 (см. рис. 11). Инструкции прерывания не разбросаны по всему коду, а сгруппированы в собственных функциях-обертках. Конечно, вирус тоже может "обмазать" системные вызовы слоем переходного кода, но вряд ли у него получится подделать характер оберток конкретного заражаемого файла.



Рисунок 11 0x00E фрагмент "честного" файла cat из комплекта поставки Free BSD, аккуратно размещающего native?API вызовы в функциях-обертках

Некоторые (впрочем, довольно немногочисленные) вирусы так просто не сдаются и используют различные методики, затрудняющие их анализ и обнаружение. Наиболее талантливые (или скорее прилежные) разработчики динамически генерируют инструкцию INT 80h/CALL 0007:00000000 на лету и, забрасывая ее на верхушку стека, скрытно передают ей управление. Как следствие – в дизассемблерном листинге исследуемой программы вызов INT 80h/INT 80h/CALL 0007:00000000 будет отсутствовать, и обнаружить такие вирусы можно лишь по многочисленным косвенным вызовам подпрограмм, находящихся в стеке. Это действительно нелегко, т. к. косвенные вызовы в изобилии присутствуют и в "честных" программах, а определение значений вызываемых адресов представляет собой серьезную проблему (во всяком случае при статическом анализе). С другой стороны, таких вирусов пока существует немного (да и те – сплошь лабораторные), так что никаких поводов для паники пока нет. А вот шифрование критических к раскрытию участков вирусного тела встречается гораздо чаще. Однако для дизассемблера IDA PRO это не бог весь какая сложная проблема, и даже многоуровневая шифровка снимается без малейшего умственного и физического напряжения.



Впрочем, на каждую старуху есть проруха, и IDA Pro тому не исключение. При нормальном развитии событий IDA Pro автоматически определяет имена вызываемых функций, оформляя их как комментарии. Благодаря этому замечательному обстоятельству, для анализа исследуемого алгоритма нет нужды постоянно лезть в справочник. Такие вирусы, как, например, Linux.ZipWorm, не могут смириться с подобным положением дел и активно используют специальные приемы программирования, сбивающие дизассемблер с толку. Тот же Linux.ZipWorm проталкивает номера вызываемых функций через стек, что вводит IDA в замешательство, лишая ее возможности определения имен последних:

.text:080483C0             push   13h

.text:080483C2             push   2

.text:080483C4             sub    ecx, ecx

.text:080483C6             pop    edx

.text:080483C7             pop    eax    ; // EAX := 2. это

вызов fork

.text:080483C8             int    80h    ; LINUX - ß IDA

не смогла определить имя вызова!

Листинг 14 фрагмент вируса Linux.ZipWorm, активно и небезуспешно противостоящего дизассемблеру IDA Pro

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

Уши будут торчать еще и потому, что большинство вирусов никак не заботится о создании стартового кода или хотя бы плохонькой его имитации. В точке входа "честной" программы всегда (ну, или практически всегда) расположена нормальная функция с классическим прологом и эпилогом, автоматически распознаваемая дизассемблером IDA Pro, вот например:



text:080480B8 start  proc near

text:080480B8

text:080480B8        push   ebp

text:080480B9        mov    ebp, esp

text:080480BB        sub    esp, 0Ch



text:0804813B        ret

text:0804813B start  endp

Листинг 15 пример нормальной стартовой функции с классическим прологом и эпилогом

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

.text:08048330       public start

.text:08048330       start  proc near

.text:08048330             xor    ebp, ebp

.text:08048332             pop    esi

.text:08048333             mov    ecx, esp

.text:08048335             and    esp, 0FFFFFFF8h

.text:08048338             push   eax

.text:08048339             push   esp

.text:0804833A             push   edx

.text:0804833B             push   offset sub_804859C

.text:08048340             push   offset sub_80482BC

.text:08048345             push   ecx

.text:08048346             push   esi

.text:08048347             push   offset loc_8048430

.text:0804834C             call   ___libc_start_main

.text:08048351             hlt

.text:08048352             nop

.text:08048353             nop

.text:08048353       start  endp

Листинг 16 альтернативный пример нормальной стартовой функции

Большинство зараженных файлов выглядит иначе. В частности, стартовый код вируса PolyEngine.Linux.LIME.poly выглядит так:

.data:080499C1 LIME_END:                        ; Alternative name is 'main'

.data:080499C1             mov    eax, 4

.data:080499C6             mov    ebx, 1

.data:080499CB             mov    ecx, offset gen_msg  ; "Generates 50 [LiME] encrypted…"

.data:080499D0             mov    edx, 2Dh

.data:080499D5             int    80h                  ; LINUX - sys_write

.data:080499D7             mov    ecx, 32h

Листинг 17 стартовый код вируса PolyEngine.Linux.LIME.poly


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