подразумевает модификацию внутреннего списка процессов и отвязывание от него дескриптора task_struct нужного процесса. Но здесь возникает проблема: этот список используется планировщиком, и если в нем не будет описателя процесса, то процесс повиснет, поскольку планировщик не может узнать о его существовании. Нужно еще и изменять логику работы планировщика, что, в принципе, возможно, но неплохо усложняет задачу злоумышленнику.
Маскировка сетевых соединений и модификация трафика
Чтобы скрыть бэкдор, руткиты либо применяют технику Port knocking, либо подделывают информацию об открытых сокетах. Пользовательские программы, в числе которых netstat, для получения информации о сетевых соединениях используют псевдофайлы /proc/net/tcp и /proc/net/tcp6, служащие отображением данных из памяти ядра. Перехват vfs_read() позволяет руткиту фильтровать соединения, доступные глазу юзера из этого файла. Можно также перехватить tcp4_seq_show() и tcp6_seq_show(), с помощью которых реализуются эти интерфейсы ядра в /proc. Впрочем, утилита ss работает немного иначе и в некоторых случаях может отобразить скрытые из /proc/net/tcp и tcp6 соединения.
Встроенный в ядро Linux межсетевой экран NetFilter обеспечивает фильтрацию пакетов, трансляцию адресов и прочие преобразования пакетов. Эта подсистема представляет собой набор хуков над стеком сетевых протоколов Linux. С их помощью можно регистрировать в ядре функции для работы с пакетами на одной из пяти стадий их обработки: PREROUTING, INPUT (LOCAL IN), FORWARD, POSTROUTING и OUTPUT (LOCAL OUT). «Ядерный» руткит легко может зарегистрировать свою функцию для модификации сетевого трафика на любом из этапов.
Способы борьбы с LKM-руткитами
Несмотря на изощренные способы маскировки руткитов, установить, что система заражена, зачастую не так сложно. Руткиты перехватывают функции, а тут, как показано ранее, вариантов немного.
В случаях с подменой адресов лучше всего иметь исходный вариант таблицы, чтобы впоследствии можно было проверить ее целостность, но нужно помнить, что после обновления ядра адреса могут измениться. Для сплайсинга можно проверить, нет ли по адресу функции байта со значением 0x90, соответствующего jmp, и выявить хук (что уже не столь просто, если он установлен не в начале).
Обнаружить скрытые соединения в простейшей ситуации можно внешним сканером портов, но при использовании Port knocking это вряд ли даст результат, разве что придется мониторить трафик, проходящий через сетевой шлюз.
Еще есть интересная задача — поискать в памяти ядра дескриптор модуля, отвязанного от списка загруженных модулей. Найдя, можно вернуть его в список, чтобы затем использовать rmmod (если, конечно, руткит не подменил и его на всякий случай). Существует решение (см. раздел 6), позволяющее обнаружить в памяти объекты, похожие на дескрипторы LKM, но оно работает на уже очень старых и только 32-битных ядрах. Перебор же памяти современных систем затруднен из-за огромного адресного пространства, но оттого эта задача становится лишь интересней.
Однако установить наличие руткита в системе — лишь полдела. Неплохо бы его оттуда убрать, а еще и найти сам исполняемый файл, чтобы было что поизучать на выходных. Самый простой и безотказный способ — взять носитель с зараженной операционкой и искать там подозрительные файлы загружаемых модулей со сторонней машины, хотя это не поможет против одного сверхсекретного метода хранения файлов (кто знаком с такими вредоносами, тоже пишите). Но это совсем не интересно, правда? К тому же наверняка существуют системы, для которых пребывать в нерабочем состоянии, пока идет поиск, крайне нежелательно. Поэтому попробуем исследовать зараженную машину изнутри. Что же может выдать присутствие руткита и при этом указать на файл вредоносного модуля?
Начинаем собственное расследование
Итак, что нам известно? Что LKM-руткит непременно должен где-то прописать строчку, ведущую к его загрузке при старте ОС. Также мы знаем, что он старается это скрыть.
Предположим, в нашей системе установлен Reptile. Открываем в любом текстовом редакторе /etc/modules и видим, что он будто бы чист, но мы-то знаем — там есть скрытое содержимое. Что будет, если попробовать, не внося никаких изменений в файл, сохранить его? Сохранится именно то, что мы видим, — то есть после перезагрузки ОС вредоносный LKM уже активен не будет. Правда, здорово? Автор руткита EnyeLKM тоже предлагал такой способ предотвращения его загрузки. Описанный эффект наблюдается потому, что сохраняется ровно то, что открыто в редакторе, то есть часть файла, свободная от данных руткита.
Что ж, уже неплохо. Но что делать, если неизвестно, в какой именно файл автозагрузки прописался руткит? Не перебирать же все задействованные файлы (хотя можно, но это слишком просто). К тому же такой способ уничтожает в файле все зацепки, которые помогли бы быстрее идентифицировать вредонос. Нужно найти модифицированный файл автозагрузки, поместить вместо него свободную от данных руткита копию, но при этом как-то оставить эти данные, чтобы было проще найти вредоносный LKM. Еще было бы полезно определить, какой именно файл был модифицирован руткитом, чтобы не пересохранять и анализировать их все.
Таким образом, наша задача предотвращения загрузки LKM-руткита сводится к двум вопросам:
Папка-невидимка и несоответствие между размером /etc/modules и количеством считанных из него байтов
Со вторым вопросом тоже несложно разобраться. Переименуем найденный на предыдущем шаге файл, чтобы система не читала из него при следующем запуске, а затем сохраним копию (которая уже не будет содержать данные руткита) под оригинальным именем. Вуаля!
Разрабатываем арсенал
Теперь мы готовы автоматизировать поиск и восстановление модифицированного файла автозагрузки. Необходим лишь список этих файлов, чтобы программа знала, где именно искать подвох. Далее для каждого такого файла программа сравнивает количество байтов, прочитанных с помощью fread() (за которой стоит системный вызов read(), а за ним, в свою очередь, перехваченная функция ядра vfs_read()), с размером файла, полученным из структуры, описывающей файл в файловой системе (i-node). Так как данная структура недоступна из пространства юзера, необходимо воспользоваться системным вызовом fstat().
Безусловно, LKM-руткиту не составит особого труда перехватить и его, но мне хотелось показать, что нынешним «ядерным» руткитам можно успешно противостоять из пространства пользователя, поэтому я предлагаю программу пользовательского уровня. При необходимости ее можно портировать в ядро. Там подделать размер файла руткит сможет, только подменив значение f->inode->i_size (для ядер старше 3.9.0 — f->f_mapping->host->i_size), но это как минимум чревато проблемами при чтении файла — все-таки серьезная низкоуровневая структура. В любом случае известные ныне руткиты не влияют ни на эти данные, ни на fstat() (смею предположить, что теперь начнут).
Код разработанной мной программы можно найти на гитхабе. Она предлагает минимальный набор функций, демонстрирующий, что справляется со своей задачей; при желании его можно и нужно расширить. Программа содержит массив строк с именами файлов, наиболее уязвимых к модификации LKM-руткитами (список еще далеко не полный), и проверяет каждый из них на наличие маскируемого содержимого.
Основная часть этой проверки заключена в функции cmp_size(). При подозрении на руткит выводится соответствующее сообщение с информацией о том, на какое количество байтов отличается фактическое содержимое от прочитанного. get_fsize() получает размер проверяемого файла из его дескриптора:
Код:Скопировать в буфер обмена
off_t get_fsize(FILE *f)
{
int res;
struct stat fst;
errno = 0;
res = fstat(fileno(f), &fst);
if(res){
perror("In get_fsize(): couldn't get fstat");
return 0;
}
return fst.st_size;
}
short cmp_size(FILE *f)
{
unsigned int i_size, read;
char *fbuf;
i_size = (unsigned int)get_fsize(f);
if (errno){
printf("\x1b[1;31mWARN\x1b[0m Some problems with %s.\n",
start_files);
return 1;
}
fbuf = (char*)malloc((i_size+1) * sizeof(char));
memset(fbuf, 0, i_size+1);
read = fread(fbuf, 1, i_size, f);
if (i_size != read){
printf("\x1b[1;31mWARN\x1b[0m Something performs file tampering of %s : "
"read %u bytes instead of %u.\n", start_files, read, i_size);
lets_talk(f, i_size, read);
free(fbuf);
return 1;
}else{
printf("\x1b[32m%s\x1b[0m looks fine to the userland\n", start_files);
free(fbuf);
return 0;
}
}
Если найдено несоответствие, программа предложит возможные действия: попытку прочесть действительное содержимое файла (побайтово, чтобы руткит не смог найти свои маркеры в считанном буфере. Это не дает никаких гарантий, но почему бы не попробовать?) и заменить его безопасной копией. К старому файлу с данными руткита при этом добавляется суффикс .old.
nitara-l3 в действии
Теперь осталось перезагрузиться, ведь руткит еще сидит в памяти и занимается своими грязными делами. Ну а дальше — ищем то, что скрывалось в ныне чистом файле автозагрузки, находим вредоносный бинарник, проверяем его на VirusTotal или где-нибудь еще и радуемся, какие мы молодцы.
Находим вредоносный LKM после перезагрузки
Справедливости ради скажу: против руткитов, отслеживающих обращения к своему файлу автозагрузки и проверяющих его содержимое при записи, описанные выше методы могут не помочь, но это уже, как говорится, совсем другая история. Мы тут рассматривали вполне определенный частный случай, но кто знает, вдруг сама идея, к которой мы пришли, будет полезна и в дальнейшем?
Напоследок
Ясное дело, что противостояние антивирусов и вредоносных программ — это вечная борьбабобра с ослом добра со злом, и чем дальше, тем более изощренные тактики злоумышленники используют для своих злодеяний.
Как ты понимаешь, самым надежным способом выявить руткит все равно будет анализ диска, хотя бы с помощью Live CD. И лишь после, имея на руках вредонос и изучив все его механизмы, можно браться за разработку защиты непосредственно для взломанной оси, что мы и попробовали сделать на одном конкретном примере. Против будущих и неизвестных сейчас LKM-руткитов наша программа, вероятно, окажется бесполезна, но это лишний повод вновь почувствовать себя супергероем и совершенствовать методы противодействия.
Если тебе, дорогой читатель, интересно изучение всяких нехороших программ под Linux, в особенности руткитов, держи несколько полезных ссылок, которые здорово помогли мне в этой области.
WWW
Маскировка сетевых соединений и модификация трафика
Чтобы скрыть бэкдор, руткиты либо применяют технику Port knocking, либо подделывают информацию об открытых сокетах. Пользовательские программы, в числе которых netstat, для получения информации о сетевых соединениях используют псевдофайлы /proc/net/tcp и /proc/net/tcp6, служащие отображением данных из памяти ядра. Перехват vfs_read() позволяет руткиту фильтровать соединения, доступные глазу юзера из этого файла. Можно также перехватить tcp4_seq_show() и tcp6_seq_show(), с помощью которых реализуются эти интерфейсы ядра в /proc. Впрочем, утилита ss работает немного иначе и в некоторых случаях может отобразить скрытые из /proc/net/tcp и tcp6 соединения.
Встроенный в ядро Linux межсетевой экран NetFilter обеспечивает фильтрацию пакетов, трансляцию адресов и прочие преобразования пакетов. Эта подсистема представляет собой набор хуков над стеком сетевых протоколов Linux. С их помощью можно регистрировать в ядре функции для работы с пакетами на одной из пяти стадий их обработки: PREROUTING, INPUT (LOCAL IN), FORWARD, POSTROUTING и OUTPUT (LOCAL OUT). «Ядерный» руткит легко может зарегистрировать свою функцию для модификации сетевого трафика на любом из этапов.
Способы борьбы с LKM-руткитами
Несмотря на изощренные способы маскировки руткитов, установить, что система заражена, зачастую не так сложно. Руткиты перехватывают функции, а тут, как показано ранее, вариантов немного.
В случаях с подменой адресов лучше всего иметь исходный вариант таблицы, чтобы впоследствии можно было проверить ее целостность, но нужно помнить, что после обновления ядра адреса могут измениться. Для сплайсинга можно проверить, нет ли по адресу функции байта со значением 0x90, соответствующего jmp, и выявить хук (что уже не столь просто, если он установлен не в начале).
Обнаружить скрытые соединения в простейшей ситуации можно внешним сканером портов, но при использовании Port knocking это вряд ли даст результат, разве что придется мониторить трафик, проходящий через сетевой шлюз.
Еще есть интересная задача — поискать в памяти ядра дескриптор модуля, отвязанного от списка загруженных модулей. Найдя, можно вернуть его в список, чтобы затем использовать rmmod (если, конечно, руткит не подменил и его на всякий случай). Существует решение (см. раздел 6), позволяющее обнаружить в памяти объекты, похожие на дескрипторы LKM, но оно работает на уже очень старых и только 32-битных ядрах. Перебор же памяти современных систем затруднен из-за огромного адресного пространства, но оттого эта задача становится лишь интересней.
Однако установить наличие руткита в системе — лишь полдела. Неплохо бы его оттуда убрать, а еще и найти сам исполняемый файл, чтобы было что поизучать на выходных. Самый простой и безотказный способ — взять носитель с зараженной операционкой и искать там подозрительные файлы загружаемых модулей со сторонней машины, хотя это не поможет против одного сверхсекретного метода хранения файлов (кто знаком с такими вредоносами, тоже пишите). Но это совсем не интересно, правда? К тому же наверняка существуют системы, для которых пребывать в нерабочем состоянии, пока идет поиск, крайне нежелательно. Поэтому попробуем исследовать зараженную машину изнутри. Что же может выдать присутствие руткита и при этом указать на файл вредоносного модуля?
Начинаем собственное расследование
Итак, что нам известно? Что LKM-руткит непременно должен где-то прописать строчку, ведущую к его загрузке при старте ОС. Также мы знаем, что он старается это скрыть.
Предположим, в нашей системе установлен Reptile. Открываем в любом текстовом редакторе /etc/modules и видим, что он будто бы чист, но мы-то знаем — там есть скрытое содержимое. Что будет, если попробовать, не внося никаких изменений в файл, сохранить его? Сохранится именно то, что мы видим, — то есть после перезагрузки ОС вредоносный LKM уже активен не будет. Правда, здорово? Автор руткита EnyeLKM тоже предлагал такой способ предотвращения его загрузки. Описанный эффект наблюдается потому, что сохраняется ровно то, что открыто в редакторе, то есть часть файла, свободная от данных руткита.
Что ж, уже неплохо. Но что делать, если неизвестно, в какой именно файл автозагрузки прописался руткит? Не перебирать же все задействованные файлы (хотя можно, но это слишком просто). К тому же такой способ уничтожает в файле все зацепки, которые помогли бы быстрее идентифицировать вредонос. Нужно найти модифицированный файл автозагрузки, поместить вместо него свободную от данных руткита копию, но при этом как-то оставить эти данные, чтобы было проще найти вредоносный LKM. Еще было бы полезно определить, какой именно файл был модифицирован руткитом, чтобы не пересохранять и анализировать их все.
Таким образом, наша задача предотвращения загрузки LKM-руткита сводится к двум вопросам:
- как найти файл со спрятанным содержимым, если неизвестно, каким образом руткит закрепился в системе;
- как сохранить этот файл так, чтобы это содержимое не оказалось удалено.
Папка-невидимка и несоответствие между размером /etc/modules и количеством считанных из него байтов
Со вторым вопросом тоже несложно разобраться. Переименуем найденный на предыдущем шаге файл, чтобы система не читала из него при следующем запуске, а затем сохраним копию (которая уже не будет содержать данные руткита) под оригинальным именем. Вуаля!
Разрабатываем арсенал
Теперь мы готовы автоматизировать поиск и восстановление модифицированного файла автозагрузки. Необходим лишь список этих файлов, чтобы программа знала, где именно искать подвох. Далее для каждого такого файла программа сравнивает количество байтов, прочитанных с помощью fread() (за которой стоит системный вызов read(), а за ним, в свою очередь, перехваченная функция ядра vfs_read()), с размером файла, полученным из структуры, описывающей файл в файловой системе (i-node). Так как данная структура недоступна из пространства юзера, необходимо воспользоваться системным вызовом fstat().
Безусловно, LKM-руткиту не составит особого труда перехватить и его, но мне хотелось показать, что нынешним «ядерным» руткитам можно успешно противостоять из пространства пользователя, поэтому я предлагаю программу пользовательского уровня. При необходимости ее можно портировать в ядро. Там подделать размер файла руткит сможет, только подменив значение f->inode->i_size (для ядер старше 3.9.0 — f->f_mapping->host->i_size), но это как минимум чревато проблемами при чтении файла — все-таки серьезная низкоуровневая структура. В любом случае известные ныне руткиты не влияют ни на эти данные, ни на fstat() (смею предположить, что теперь начнут).
Код разработанной мной программы можно найти на гитхабе. Она предлагает минимальный набор функций, демонстрирующий, что справляется со своей задачей; при желании его можно и нужно расширить. Программа содержит массив строк с именами файлов, наиболее уязвимых к модификации LKM-руткитами (список еще далеко не полный), и проверяет каждый из них на наличие маскируемого содержимого.
Основная часть этой проверки заключена в функции cmp_size(). При подозрении на руткит выводится соответствующее сообщение с информацией о том, на какое количество байтов отличается фактическое содержимое от прочитанного. get_fsize() получает размер проверяемого файла из его дескриптора:
Код:Скопировать в буфер обмена
off_t get_fsize(FILE *f)
{
int res;
struct stat fst;
errno = 0;
res = fstat(fileno(f), &fst);
if(res){
perror("In get_fsize(): couldn't get fstat");
return 0;
}
return fst.st_size;
}
short cmp_size(FILE *f)
{
unsigned int i_size, read;
char *fbuf;
i_size = (unsigned int)get_fsize(f);
if (errno){
printf("\x1b[1;31mWARN\x1b[0m Some problems with %s.\n",
start_files);
return 1;
}
fbuf = (char*)malloc((i_size+1) * sizeof(char));
memset(fbuf, 0, i_size+1);
read = fread(fbuf, 1, i_size, f);
if (i_size != read){
printf("\x1b[1;31mWARN\x1b[0m Something performs file tampering of %s : "
"read %u bytes instead of %u.\n", start_files, read, i_size);
lets_talk(f, i_size, read);
free(fbuf);
return 1;
}else{
printf("\x1b[32m%s\x1b[0m looks fine to the userland\n", start_files);
free(fbuf);
return 0;
}
}
Если найдено несоответствие, программа предложит возможные действия: попытку прочесть действительное содержимое файла (побайтово, чтобы руткит не смог найти свои маркеры в считанном буфере. Это не дает никаких гарантий, но почему бы не попробовать?) и заменить его безопасной копией. К старому файлу с данными руткита при этом добавляется суффикс .old.
nitara-l3 в действии
Теперь осталось перезагрузиться, ведь руткит еще сидит в памяти и занимается своими грязными делами. Ну а дальше — ищем то, что скрывалось в ныне чистом файле автозагрузки, находим вредоносный бинарник, проверяем его на VirusTotal или где-нибудь еще и радуемся, какие мы молодцы.
Находим вредоносный LKM после перезагрузки
Справедливости ради скажу: против руткитов, отслеживающих обращения к своему файлу автозагрузки и проверяющих его содержимое при записи, описанные выше методы могут не помочь, но это уже, как говорится, совсем другая история. Мы тут рассматривали вполне определенный частный случай, но кто знает, вдруг сама идея, к которой мы пришли, будет полезна и в дальнейшем?
Напоследок
Ясное дело, что противостояние антивирусов и вредоносных программ — это вечная борьба
Как ты понимаешь, самым надежным способом выявить руткит все равно будет анализ диска, хотя бы с помощью Live CD. И лишь после, имея на руках вредонос и изучив все его механизмы, можно браться за разработку защиты непосредственно для взломанной оси, что мы и попробовали сделать на одном конкретном примере. Против будущих и неизвестных сейчас LKM-руткитов наша программа, вероятно, окажется бесполезна, но это лишний повод вновь почувствовать себя супергероем и совершенствовать методы противодействия.
Если тебе, дорогой читатель, интересно изучение всяких нехороших программ под Linux, в особенности руткитов, держи несколько полезных ссылок, которые здорово помогли мне в этой области.
WWW
- Очень хороший обширный обзор руткитов уровня ядра. Описаны всевозможные методики, применяемые ими для «невидимости», и варианты защиты от них. Не смотри, что ему уже пятнадцать лет, — как минимум многие базовые механизмы ядра Linux в целом остались такими же.
- Серия из 35 статей о работе и программировании модулей ядра Linux.
- Один добрый человек собрал список Linux-руткитов со ссылками на исходники.
- Нельзя не упомянуть такую фундаментальную работу Таненбаума, как «Современные операционные системы», а также исходники и документацию ядра Linux, ведь для действительно хорошего понимания работы вредоносных программ необходимо знать, как устроена целевая система.