На правах врезки "основные признаки вирусов"
Искажение структуры исполняемых файлов– характерный, но недостаточный признак вирусного заражения. Быть может, это защита хитрая такая или завуалированный способ самовыражения разработчика. К тому же некоторые вирусы ухитряются внедриться в файл практически без искажений его структуры. Однозначный ответ дает лишь полное дизассемблирование исследуемого файла, однако это слишком трудоемкий способ, требующий усидчивости, глубоких знаний операционной системы и неограниченного количества свободного времени. Поэтому на практике обычно прибегают к компромиссному варианту, сводящемуся к беглому просмотру дизассемблерного листинга на предмет поиска основных признаков вирусного заражения.
Большинство вирусов использует довольно специфический набор машинных команд и структур данных, практически никогда не встречающихся в "нормальных" приложениях. Конечно, разработчик вируса при желании может все это скрыть и распознать зараженный код тогда не удастся. Но это в теории. На практике же вирусы обычно оказываются настолько тупы, что обнаруживаются за считанные доли секунды.
Ведь чтобы заразить жертву, вирус прежде должен ее найти, отобрав среди всех кандидатов только файлы "своего" типа. Для определенности возьмем 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