Записки исследователя компьютерных вирусов
Шрифт:
Более совершенные вирусы сканируют стартовую процедуру заражаемого файла в поисках инструкций call или jmp. A найдя таковую – подменяют вызываемый адрес на адрес своего тела. Несмотря на кажущуюся неуловимость, обнаружить такой способ перехвата управления очень легко. Первое и главное – вирус, в отличие от легально вызываемых функций, никак не использует переданные ему в стеке аргументы. Он не имеет никаких понятий об их числе и наличии (машинный анализ количества переданных аргументов немыслим без интеграции в вирус полноценного дизассемблера, оснащенного мощным интеллектуальным анализатором). Вирус тщательно сохраняет все изменяемые регистры, опасаясь, что функции могут использовать регистровую передачу аргументов с неизвестным ему соглашением. Самое главное – при передаче управления оригинальной функции вирус должен либо удалить с верхушки стека адрес возврата (в противном случае их там окажется
Вирусы, перехватывающие управление в произвольной точке программы (зачастую чрезвычайно удаленной от точки входа), выявить намного труднее, поскольку приходится анализировать довольно большие, причем заранее не определенные объемы кода. Впрочем, с удалением от точки входа стремительно возрастает риск, что данная ветка программы никогда не получит управление, поэтому все известные мне вирусы не выходят за границы первого встретившегося им ret.
Основные признаки вирусов
Искажение структуры исполняемых файлов – характерный, но недостаточный признак вирусного заражения. Быть может, это защита хитрая такая или завуалированный способ самовыражения разработчика. К тому же некоторые вирусы ухитряются внедриться в файл практически без искажений его структуры. Однозначный ответ дает лишь полное дизассемблирование исследуемого файла, однако это слишком трудоемкий способ, требующий усидчивости, глубоких знаний операционной системы и неограниченного количества свободного времени. Поэтому на практике обычно прибегают к компромиссному варианту, сводящемуся к беглому просмотру дизассемблерного листинга на предмет поиска основных признаков вирусного заражения.
Большинство вирусов использует довольно специфический набор машинных команд и структур данных, практически никогда не встречающихся в «нормальных» приложениях. Конечно, разработчик вируса при желании может все это скрыть и распознать зараженный код тогда не удастся. Но это в теории. На практике же вирусы обычно оказываются настолько тупы, что обнаруживаются за считанные доли секунды.
Ведь чтобы заразить жертву, вирус прежде должен ее найти, отобрав среди всех кандидатов только файлы «своего» типа. Для определенности возьмем elf-файл. Тогда вирус будет вынужден считать его заголовок и сравнить четыре первых байта со строкой | FELF, которой соответствует ASCII-последовательность 7F 45 4С 46. Конечно, если тело вируса зашифровано, вирус использует хеш-сравнение или же другие хитрые приемы программирования, строки ELF в теле зараженного файла не окажется, но более чем в половине всех существующих UNIX-вирусов она все-таки есть, и этот прием, несмотря на свою изумительную простоту, очень неплохо работает.
Загрузите исследуемый файл в любой hex-редактор и попробуйте отыскать строку « | ELF». В зараженном файле таких строк будет две: одна – непосредственно в заголовке, другая – в кодовой секции или секции данных. Только не используйте дизассемблер! Очень многие вирусы преобразуют строку | FELF в 32-разрядную целочисленную константу 464C457Fh, которая маскирует присутствие вируса, но при переключении в режим дампа сразу же «проявляется» на экране. Далее приведен внешний вид файла, зараженного вирусом VirTool.Linux. Mmap.443, который использует именно такую методику (рис. 2.9).
Рис. 2.9. Фрагмент файла, зараженного вирусом VirTool.Linux.Mmap.443. ВНЕХ-дампе легко обнаруживается строка ELF, используемая вирусом для поиска жертв «своего» типа
Вирус Linux.Winter.343 (также известный под именем Lotek) по этой методике обнаружить не удается, поскольку он использует специальное математическое преобразование, зашифровывая строку ELF на лету (листинг 2.14).
Листинг 2.14. Фрагмент вируса Lotek, тщательно скрывающего свой интерес к elf-файлам
Непосредственное значение В9ВЗВА81h, соответствующее текстовой строке 'B||† (в приведенном выше листинге оно выделено жирным шрифтом), представляет собой не что иное, как строку ЫELF, преобразованную в 32-разрядную константу и умноженную на минус единицу. Складывая полученное значение с четырьмя первыми байтами жертвы, вирус получает ноль, если строки равны, и ненулевое значение в противном случае.
Как вариант, вирус может дополнять эталонную строку ELF до единицы, и тогда в его теле будет присутствовать последовательность 80 ВА ВЗ В9. Реже встречаются циклические сдвиги на одну, две, три… и семь позиций в различные стороны, неполные проверки (то есть проверки на совпадение двух или трех байт) и некоторые другие операции –
все не перечислишь!Более уязвимым с точки зрения скрытности является механизм реализации системных вызовов. Вирус не может позволить себе тащить за собой всю библиотеку 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 80 h/CALL 0007:0000000 (CD 80/9 A 00 00 00 00 07 00) с высокой степенью вероятности свидетельствует о наличии вируса. Для предотвращения ложных срабатываний (то есть обнаружения вируса там, где и следов его нет), вы должны не только обнаружить обращения к native-API, но и проанализировать последовательность их вызовов. Для вирусов характерна следующая цепочка системных команд: sys_open, sys_lseek, ol djnmap/sysjTiunmap, 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 (рис. 2.10). Наличие незамаскированных вызовов INT 80h с легкостью разоблачает агрессивную природу программного кода, указывая на склонность последнего к саморазмножению.
А вот так для сравнения выглядят системные вызовы «честной» программы – утилиты cat из комплекта поставки Free BSD 4.5 (рис. 2.11). Инструкции прерывания не разбросаны по всему коду, а сгруппированы в собственных функциях-обертках. Конечно, вирус тоже может «обмазать» системные вызовы слоем переходного кода, но вряд ли у него получится подделать характер оберток конкретного заражаемого файла.
Некоторые (впрочем, довольно немногочисленные) вирусы так просто не сдаются и используют различные методики, затрудняющие их анализ и обнаружение. Наиболее талантливые (или, скорее, прилежные) разработчики динамически генерируют инструкцию INT 80 h/CALL 0007:00000000 на лету и, забрасывая ее на верхушку стека, скрытно передают ей управление. Как следствие – в дизассемблерном листинге исследуемой программы вызов INT 80 h/INT 80 h/CALL 0007:00000000 будет отсутствовать, и обнаружить такие вирусы можно лишь по многочисленным косвенным вызовам подпрограмм, находящихся в стеке. Это действительно нелегко, так как косвенные вызовы в изобилии присутствуют и в «честных» программах, а определение значений вызываемых адресов представляет собой серьезную проблему (во всяком случае, при статическом анализе). Вместе с тем таких вирусов пока существует немного (да и те – сплошь лабораторные), так что никаких поводов для паники пока нет. А вот шифрование критических к раскрытию участков вирусного тела встречается гораздо чаще. Однако для дизассемблера IDA PRO это не бог весть какая сложная проблема, и даже многоуровневая шифровка снимается без малейшего умственного и физического напряжения.
Рис. 2.10. Фрагмент файла, зараженного вирусом VirTool.Linux.Mmap.443, демаскирующим свое присутствие прямым обращением к native-API операционной системы
Впрочем, на каждую старуху есть проруха, и IDA Pro тому не исключение. При нормальном развитии событий IDA Pro автоматически определяет имена вызываемых функций, оформляя их как комментарии. Благодаря этому замечательному обстоятельству для анализа исследуемого алгоритма нет нужды постоянно лезть в справочник. Такие вирусы, как, например, Linux.ZipWorm, не могут смириться с подобным положением дел и активно используют специальные приемы программирования, сбивающие дизассемблер с толку. Тот же Linux.ZipWorm проталкивает номера вызываемых функций через стек, что вводит IDA в замешательство, лишая ее возможности определения имен последних (листинг 2.15).