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

       

Сжатие части оригинального файла


Древние считали, что истина в вине. Они явно ошибались. Истина в том, что день ото дня программы становятся все жирнее и жирнее, а вирусы все изощреннее и изощреннее. Какой бы уродливый код не выбрасывала на рынок фирма Microsoft, он все же лучше некоторых UNIX-подделок. Например файл cat, входящий в FreeBSD 4.5, занимает более 64 Кб. Не слишком ли много для простенькой утилиты?!

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

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


Различные программы содержат различное количество свободного места, расходующегося на выравнивание. В частности, программы, входящие в базовый комплект поставки Free BSD 4.5, преимущественно откомилированы с выравниванием на величину 4'х байт. Учитывая, что команда безусловного перехода в x86-системах занимает по меньшей мере два байта, втиснуться в этот скромный объем вирусу просто нереально. С операционной системой Red Hat 5.0 дела обстоят иначе. Кратность выравнивания, установленная на величину от 08h до 10h байт, с легкостью вмещает в себя вирус средних размеров.

Ниже в качестве примера приведен фрагмент дизассемблерного листинга утилиты PING, зараженной вирусом UNIX.NuxBe.quilt (модификация известного вируса NuxBee, опубликованного в электронном журнале, выпускаемом группой #29A).

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

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

.text:08000BD9             xor    eax, eax

.text:08000BDB             xor    ebx, ebx

.text:08000BDD             jmp    short loc_8000C01



.text:08000C01 loc_8000C01:                     ; CODE XREF: .text:0800BDD^j



.text:08000C01             mov    ebx, esp



.text:08000C03             mov    eax, 90h

.text:08000C08             int    80h                  ; LINUX - sys_msync

.text:08000C0A             add    esp, 18h

.text:08000C0D             jmp    loc_8000D18



.text:08000D18 loc_8000D18:                      ; CODE XREF: .text:08000C0D^j

.text:08000D18             dec    eax

.text:08000D19             jns    short loc_8000D53

.text:08000D1B             jmp    short loc_8000D2B



.text:08000D53 loc_8000D53:                     ; CODE XREF: .text:08000D19^j

.text:08000D53             inc    eax

.text:08000D54             mov    [ebp+8000466h], eax

.text:08000D5A             mov    edx, eax

.text:08000D5C             jmp    short loc_8000D6C

Листинг 6 фрагмент файла, зараженного вирусом UNIX.NuxBe.quilt, "размазывающим" себя по кодовой секции

Кстати говоря, рассмотренный нами алгоритм не совсем корректен. Цепочка NOP'ов может встреться в любом месте программы (например внутри функции) и тогда зараженный файл перестанет работать. Чтобы этого не произошло, некоторые вирусы выполняют ряд дополнительных проверок, в частности убеждаются, что NOP'ы расположены между двумя функциями, опознавая их по командам пролога/эпилога.

Внедрение в секцию данных осуществляется еще проще. Вирус ищет длинную цепочку нулей, разделенную читабельными (точнее – printable) ASCII-символами и, найдя таковую, полагает, что он находится на ничейной территории, образовавшейся в результате выравнивая текстовых строк. Поскольку текстовые строки все чаще располагают в секции .rodata, доступной лишь на чтение, вирус должен быть готов сохранять все модифицируемые им ячейки на стеке и/или динамической памяти.

Забавно, но вирусы этого типа достаточно трудно обнаружить. Действительно, наличие не читабельных ASCII-символов между текстовыми строками – явление вполне нормальное. Может быть, это смещения или еще какие структуры данных, на худой конец – мусор, оставленный линкером!

Взгляните на рисунок 5, приведенный ниже. Согласитесь, что факт зараженности файла вовсе не так очевиден:





Рисунок 5 0x00A/0x00B. так выглядел файл cat до (слева) и после (справа) его заражения

Исследователи, имеющие некоторый опыт работы с IDA, здесь, возможно, возразят: мол, какие проблемы? Подогнал курсор к первому символу, следующему за концом ASCIIZ-строки, нажал на <C> и… дизассемблер мгновенно распахнул код вируса, живописно вплетенный в текстовые строки (см. листинг 7). На самом деле так случается только в теории. Среди нечитабельных символов вируса присутствуют и читабельные тоже. Эвристический анализатор IDA, ошибочно приняв последние за "настоящие" текстовые строки, просто не позволит их дизассемблировать. Ну, во всяком случае, до тех пор, пока они явно не будут "обезличены" нажатием клавиши <U>. К тому же вирус может вставлять в начало каждого своего фрагмента специальный символ, являющийся частью той или иной машинной команды и сбивающий дизассемблер с толку. В результате IDA дизассемблирует всего лишь один-единственный фрагмент вируса (да и тот некорректно) после чего заткнется, подталкивая нас к индуктивному выводу, что мы имеем дело с легальной структурной данных, и зловредный машинный код здесь отродясь не ночевал.

Увы! Какой бы могучей IDA ни была она все-таки не всесильна, и над всяким полученным листингом вам еще предстоит поработать. Впрочем, при некотором опыте дизассемблирования, многие машинные команды распознаются в HEX-дампе с первого взгляда (пользуясь случаем, отсылаю вас к "Технике и философии хакерских атак/дизассемблирование в уме", ставшей уже библиографической редкостью, т. к. ее дальнейших переизданий уже не планируется):

.rodata:08054140 aFileNameTooLon db 'File name too long',0

.rodata:08054153 ; ------------------------------------------------------------------

.rodata:08054153           mov    ebx, 1

.rodata:08054158           mov    ecx, 8049A55h

.rodata:08054158           jmp    loc_80541A9

.rodata:08054160 ; ------------------------------------------------------------------



.rodata:08054160 aTooManyLevelsO db ' Too many levels of symbolic links',0

.rodata:08054182 aConnectionRefu db 'Connection refused',0

.rodata:08054195 aOperationTimed db 'Operation timed out',0

.rodata:080541A9 ; ------------------------------------------------------------------

.rodata:080541A9 loc_80541A9:

.rodata:080541A9           mov    edx, 2Dh

.rodata:080541AE           int    80h           ; LINUX -

.rodata:080541B0           mov    ecx, 51000032h

.rodata:080541B5           mov    eax, 8

.rodata:080541BA           jmp    loc_80541E2

.rodata:080541BA ; ------------------------------------------------------------------

.rodata:080541BF           db     90h ; Р

.rodata:080541C0 aTooManyReferen db 'Too many references: can',27h,'t splice',0

.rodata:080541E2 ; ------------------------------------------------------------------

.rodata:080541E2 loc_80541E2:

.rodata:080541E2           mov    ecx, 1FDh

.rodata:080541E7           int    80h           ; LINUX - sys_creat

.rodata:080541E9           push   eax

.rodata:080541EA           mov    eax, 0

.rodata:080541EF           add    [ebx+8049B43h], bh

.rodata:080541F5           mov    ecx, 8049A82h

.rodata:080541FA           jmp    near ptr unk_8054288

.rodata:080541FA ; -------------------------------------------------------------------

.rodata:080541FF           db  90h ; Р

.rodata:08054200 aCanTSendAfterS db 'Can',27h,'t send after socket shutdown',0

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

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



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

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

0000E530:  00 00 00 00 FF FF FF FF ¦ 00 00 00 00 FF FF FF FF                   

0000E540:  00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00

0000E550:  00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00

0000E560:  00 47 43 43 3A 20 28 47 ¦ 4E 55 29 20 63 20 32 2E    GCC: (GNU) c 2.

0000E570:  39 35 2E 33 20 32 30 30 ¦ 31 30 33 31 35 20 28 72   95.3 20010315 (r

0000E580:  65 6C 65 61 73 65 29 20 ¦ 5B 46 72 65 65 42 53 44   elease) [FreeBSD



0000F2B0:  4E 55 29 20 63 20 32 2E ¦ 39 35 2E 33 20 32 30 30   NU) c 2.95.3 200

0000F2C0:  31 30 33 31 35 20 28 72 ¦ 65 6C 65 61 73 65 29 20   10315 (release)

0000F2D0:  5B 46 72 65 65 42 53 44 ¦ 5D 00 08 00 00 00 00 00   [FreeBSD] •



0000F2E0:  00 00 01 00 00 00 30 31 ¦ 2E

30 31 00 00 00 08 00     O   01.01   •

Листинг 8 так выглядит секция .bss большинства файлов из комплекта поставки Free BSD

Ряд дизассемблеров (и IDA Pro в том числе) по вполне логичным соображениям не загружает содержимое секций неинициализированных данных, явно отмечая это обстоятельство двойным знаком вопроса (см. листинг 9). Приходится исследовать файл непосредственно в HIEW'е или любом другом HEX-редакторе, разбирая a.out/ELF-формат "вручную", т. к. популярные HEX-редакторы его не поддерживают. Скажите честно: готовы ли вы этим реально заниматься? Так что, как ни крути, а вирусы этого типа имеют все шансы на выживание, пусть массовых эпидемий им никогда не видать.

.bss:08057560  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

.bss:08057570  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

.bss:08057580  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

.bss:08057590  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

.bss:080575A0  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

.bss:080575B0  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

.bss:080575C0  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

.bss:080575D0  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

.bss:080575E0  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? "????????????????"

Листинг 9 так выглядит секция .bss в дизассемблере IDA Pro и большинстве других дизассемблеров


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