Мариус Бурдах, перевод Владимир Куксенок1. Введение
В прошлом месяце, в первой части этой серии статей мы обсуждали подготовку и основные этапы, которые должны быть приняты во внимания при анализе работающей Linux системы, которая была скомпрометирована. Теперь мы продолжим наш анализ в поисках злонамеренного кода на работающей системе, а затем обсудим некоторые способы анализа данных, которые могут быть применены сразу же, как только данные будут переданы на наш удаленный хост.
Примечание:
Некоторые читатели после прочтения первой части этой статьи указали, что перед передачей любых цифровых данных со скомпрометированной машины, должен быть запущен доверенный шел. Я думаю, это хорошая идея, поэтому вначале мы должны статически скомпилировать, например bash, а затем скопировать его на наш сменный носитель информации. Шаг 2 должен выглядеть следующим образом:
(compromised)# /mnt/cdrom/bashМы должны помнить, что почти любой командный интерпретатор пишет каждую напечатанную команду в файл истории. Так как мы не хотим изменить локальную файловую системы, лучшим решением будет выключить запись истории команд.
Пожалуйста ознакомьтесь с первой частью этой статьи, а именно с разделами 2, 2.1, 2.2 и 2.3 (шаги с первого по пятый) прежде чем продолжить чтение.
2.3 Сбор информации о системе - продолжение
Шаг 6 - Образ физической памяти
Один из шагов в процессе сбора цифровой информации о системе это полное копирование памяти системы. Мы можем получить доступ к физической памяти напрямую копируя устройство /dev/mem или kcore файл. Файл kcore это RAM (Random Access Memory, Память со случайным доступом -- примечание переводчика) в операционной системе Linux. Он может быть найден в псевдо файловой системе, которая примонтирована в директории /proc. Размер этого файла равен размеру физической памяти, расширенной примерно до 4 Кб. Преимущество использования kcore состоит в том, что он представлен в формате ELF и поэтому может быть отлажен при помощи отладчика gdb. gdb - отличный инструмент, который позволяет трассировать и анализировать небольшие части кода или памяти. В разделе 2.7 я покажу, как использовать эту утилиту для более глубокого автономного анализа. Но в основном мы будем использовать более универсальные средства, типа grep, string и шестнадцатеричных редакторов.
В дополнение, чтобы должным образом изучать физическую память, мы должны иметь кое-какую информацию из таблицы страниц - структуры данных, использующиеся при преобразовании виртуального адреса в физический. Мы должны знать, что физическая память делится на страницы виртуальной памяти. В таблицах страниц мы можем найти информацию о порядке страниц (4 Кб на страницу в процессорах от Intel) записанных в физическую память.
Как было упомянуто ранее, мы должны помнить, что, используя ПО, работающее с динамической памятью, мы изменяем содержимое памяти. Хуже того, мы можем перезаписать возможное доказательство.
В примере ниже я скопировал образ памяти, используя псевдо файловую систему /proc:
(remote)#nc -l -p port > kcore_compromised (compromised)#/mnt/cdrom/dd < /proc/kcore | /mnt/cdrom/nc (remote) port (remote)#md5sum kcore_compromised > kcore_compromised.md5При полном копирование памяти системы мы копируем и размещенные (allocated) и не размещенные (unallocated) данные, потому что процесс копирует полный образ физической памяти. В девятом шаге мы попробуем скопировать определенный процесс, используя /proc. Должен упомянуть, что, используя /proc, мы можем изменить раздел подкачки (swap). Копируя подозрительный процесс, какие-то страницы мы заставим считаться из файла подкачки в память, а какие-то записаться в него. Другая важная особенность состоит в том, что мы копируем только ту память, в которой размещены данные процесса.
Шаг 7 - Список модулей загруженных в адресное пространство ядра операционной системы
Мы должны убедиться, что собранные данные полны и что результаты работы команд netcat и lsof не изменяются на уровне ядра. Итак, мы должны выяснить, какие модули в настоящее время загружены в память.
(remote)# nc -l -p port > lkms_compromised (compromised)#/mnt/cdrom/cat /proc/modules | /mnt/cdrom/nc (remote) port (remote)# nc -l -p port > lkms_compromised.md5 (compromised)# /mnt/cdrom/md5sum /proc/modules | /mnt/cdrom/nc (remote) portК сожалению, некоторые из злонамеренных модулей не могут быть выявлены таким способом. Чтобы проверить информацию о загруженных модулях, полученную из /proc/modules, я воспользуюсь методом, описанном в недавнем номере Phrack Magazine, в статье "Finding hidden kernel modules (the extrem way)". Модуль hunter.o проверяет цепочки модулей, загруженных в адресное пространство ядра.
(compromised)#/mnt/cdrom/insmod -f /mnt/cdrom/hunter.oВ том случае если необходимо произвести принужденную загрузку hunter.o из-за несоответствия версий, я буду использовать флаг "-f switch". Это случается когда версия ядра скомпрометированной системы отличается от версии ядра системы, на которой был скомпилирован модуль hunter.o . Лучшее решение состоит в том, чтобы пересобрать модуль для каждой версии ядра, в адресное пространство которого hunter.o будет загружен. Если мы знаем версию ядра, которое используется на скомпрометированной машине, мы может загрузить соответствующий исходный код с www.kernel.org и включить определенные заголовочные файлы того ядра в наш модуль.
(remote)#nc -l -p port > modules_hunter_compromised (compromised)#/mnt/cdrom/cat /proc/showmodules && /mnt/cdrom/dmesg | /mnt/cdrom/nc (remote) port (remote)#md5sum modules_hunter_compromised > modules_hunter_compromised.md5Теперь мы можем сравнить результаты. Также мы должны обратить внимание на размер модулей. Иногда злонамеренный код может быть внедрен в тело "легального" модуля. Последнее, что мы должны сделать - копии таблиц экспорта модулей ядра. Иногда некоторые простые lkm rootkit'ы экспортируют свои функции. Изучая файл ksyms, мы можем обнаружить присутствие злоумышленника в системе.
(remote)#nc -l -p port > ksyms_compromised (compromised)#/mnt/cdrom/cat /proc/ksyms | /mnt/cdrom/nc (remote) port (remote)# nc -l -p port > ksyms_compromised.md5 (compromised)#/mnt/cdrom/md5sum /proc/ksyms | /mnt/cdrom/nc (remote) portМы также можем использовать другие средства для обнаружения злонамеренных модулей (такие как kstat или kern_check), но, к сожалению, все они используют файл System.map . Этот файл генерируется после сборки ядра. Если администратор не скопирует этот файл и не сохранит его контрольную сумму, мы не должны доверять полученным адресам системных вызовов. Даже если адреса системных вызовов правильны, злоумышленник может использовать другие изощренные методы сокрытия злонамеренного модуля в памяти ядра. Например, adore-ng rootkit заменяет подпрограмму обработчик, осуществляющий вывод листинга директории.
Если мы уверены, что адреса системных вызовов в файле System.map или ksyms не изменялись, мы можем проверить, не изменены ли злоумышленником некоторые из адресов системных вызовов в таблице системных вызовов. Более подробно мы рассмотрим это в разделе 2.7 .
Шаг 8 - Список активных процессов
Информация обо всех процессах, открытых портах и файлах может быть собрана с помощью утилиты lsof. Естественно, этой информации можно доверять только в том случае, если мы не обнаружим в памяти lkm rootkit'ов. В дополнение, на следующем шаге мы сделаем копии подозрительных процессов. Копируя определенный процесс, мы можем отделить злонамеренные данные от памяти скомпрометированной системы, которую мы скопировали полностью. Как вы помните, мы сделали образ всей памяти системы (kcore или /dev/mem) в шаге 6.
(remote)#nc -l -p port > lsof_compromised (compromised)#/mnt/cdrom/lsof -n -P -l | /mnt/cdrom/nc (remote) port (remote)#md5sum lsof_compromised > lsof_compromised.md5Теперь мы должны проанализировать результаты работы lsof. Если хотя бы один из активных процессов кажется подозрительным, сейчас самое время сделать его копию. Также, если из результатов работы lsof мы видим, что программа, которая создала процесс, была удалена злоумышленником, у нас еще есть шансы восстановить ее. Я покажу, как сделать это в следующем разделе.
Ниже, я перечислил несколько примеров подозрительных процессов:
- Процесс слушает нетипичный TCP/UDP порт или открытый raw сокет
- Процесс имеет соединение с удаленным хостом
- Программа, удаленная после выполнения
- Файл, открытый процессом был удален (например: лог файл)
- Необычное имя процесса
- Процесс был создан несуществующим или непривилегированным пользователем
Шаг 9 - Создание копий подозрительных процессов
Я буду использовать утилиту pcat для создания образа памяти выделенной процессу.
(remote)#nc -l -p port > proc_id_compromised (compromised)#/mnt/cdrom/pcat proc_id | /mnt/cdrom/nc (remote) port (remote)#md5 proc_ip_compromised > proc_ip_compromised.md5Кроме этого, мы можем сделать копии лишь некоторых выборочных данных процесса. Более подробная информация об этом в следующем разделе.
Шаг 10 - Полезная информация о скомпрометированной системе
Наш следующий шаг - сбор некоторой полезной информации о скомпрометированной системе. Эта информация нужна для того, чтобы выполнить надлежащее описание инцидента и создать копии всех системных файлов. Помните, что все результаты вы должны отослать на доверенный хост. В таблице 3 находятся команды, которые должны быть выполнены на скомпрометированной системе.
Таблица 3: Полезная информация о скомпрометированном хосте
| Команда | Описание |
| /mnt/cdrom/cat /proc/version | Версия операционной системы |
| /mnt/cdrom/cat /proc/sys/kernel/name | Имя хоста |
| /mnt/cdrom/cat /proc/sys/kernel/domainame | Доменное имя |
| /mnt/cdrom/cat /proc/cpuinfo | Информация о аппаратном обеспечении |
| /mnt/cdrom/cat /proc/swaps | Все разделы файлов подкачки |
| /mnt/cdrom/cat /proc/partitions | Все локальные файловые системы |
| /mnt/cdrom/cat /proc/self/mounts | Примонтированные файловые системы |
| /mnt/cdrom/cat /proc/uptime | Продолжительность работы с момента загрузки системы (Uptime) |
Шаг 11 - Текущее время
Последний шаг - сбор информации о текущем времени.
(remote)#nc -l -p port > end_time (compromised)# /mnt/cdrom/date | /mnt/cdrom/nc (remote) portТеперь мы достигли того момента, когда можем выключить скомпрометированную систему. Запомните, что для завершения работы системы нельзя использовать какие-либо стандартные команды. Мы должны выдернуть силовой кабель из системного блока или источника бесперебойного питания.
2.4 Образы файловой системы
Перед выключением питания скомпрометированной системы мы сделали копии всех файловых систем и разделов подкачки, но еще не отключились от нее. Я предлагаю сделать это после выключения питания системы. Таким образом, мы сможем убедиться, что содержание скомпрометированной файловой системы больше не изменялось. Напомню, что раздел подкачки, изменялся на протяжении всего процесса сбора информации и некоторые из доказательств, возможно, были перезаписаны.
Следующий шаг состоит в загрузки операционной системы со съемного носителя. Мы можем использовать любой дистрибутив Linux, по умолчанию не монтирующий все локальные файловые системы. Теперь мы можем сделать копии всех локальных разделов или всего жесткого диска.
2.5 Основы анализа данных
Сейчас давайте снова рассмотрим процессы, которые мы упоминали в Шаге 8, где мы использовали утилиту lsof. Давайте более подробно посмотрим на эту ситуацию и рассмотрим два примера:
Пример 1: Программа, которая создала процесс, была удалена.
В этом случае, файл, который инициировал процесс, все еще находится в памяти. Чтобы его восстановить, мы должны знать идентификатор процесса, созданного программой. В каталоге /proc мы найдем исполняемый файл. Этот файл - копия удаленного. Мы должны отправить этот файл на доверенный хост.
После восстановления удаленного файла мы можем извлечь из него информацию. Мы можем выбрать один из двух вариантов: статический анализ (дизассемблированием) или динамический анализ (исполнением этого файла в контролируемой среде).
Пример 2: Файл, открытый активным процессом был удален (например: лог файл).
Фрагмент результата работы lsof представлен ниже:
smbd 3137 root rtd DIR 8,1 4096 2 / smbd 3137 root txt REG 8,1 672527 92030 /usr/bin/smbd -D smbd 3137 root mem REG 8,1 485171 44656 /lib/ld-2.2.4.so ... smbd 3137 root 16u IPv4 976 TCP *:https (LISTEN) smbd 3137 root 17u IPv4 977 TCP *:http (LISTEN) ... smbd 3137 root 20w REG 8,1 253 46934 /var/log/httpd/access_log (deleted) ...Чтобы восстановить этот файл мы должны просмотреть содержимое каталога fd (дескрипторы файлов), находящегося в директории /proc/3137.
(remote)# nc -l -p port > ls_from_proc_3137 (compromised)# /mnt/cdrom/ls -la /proc/3137/fd/ | /mnt/cdrom/nc (remote) port (remote)# more ls_from_proc_3137 l-wx------ 1 root root 64 Aug 10 21:03 12 -> /var/log/httpd/access_log (deleted) ...Как мы видим, первый файловый дескриптор удален. Все что мы должны сделать, это скопировать этот файл на доверенный хост, используя утилиту netcat.
(remote)# nc -l -p port > deleted_access_log (compromised)# /mnt/cdrom/cat /proc/3137/fd/1 | /mnt/cdrom/nc (remote) portЭто не единственный способ восстановления этого файла. Мы также можем восстановить его путем анализа освобожденных i-nodes и блоков данных.
2.6 Поиск ключевых слов
В этой части статьи я хотел бы рассказать об одном из методов исследования процессов и образов памяти. Учтите, что все описанное происходит на удаленном доверенном хосте, после получения образов памяти. Самое большое преимущество этого метода состоит в том, что мы не должны знать используемый низкоуровневый язык (видимо речь идет об ассемблере -- примечание переводчика). Я буду использовать следующие простейшие утилиты для поиска признаков вторжения:
- strings
- less
- grep
Мы соберем все печатные символы из файла образа, используя утилиту strings. Настройки по умолчанию дают нам возможность просмотреть все последовательности печатных символов, состоящие не менее чем из 4 символов. Чтобы добавить смещение относительно начала файла нужно использовать флаг "-t".
$ strings -t d kcore > kcore_strings $ md5sum kcore_strings > kcore_strings.md5Утилита grep и регулярные выражения важны на этом начальном этапе анализа. Через несколько минут мы сможем найти доказательства вторжения. Мы должны подумать, какие данные мы собираемся искать, - например, мы ищем команды, напечатанные злоумышленником, IP адреса, пароли или даже расшифрованную часть злонамеренного кода?
Ниже перечислены несколько примеров ключевых слов для поиска. Мы будем использовать их, чтобы найти доказательства вторжения в нашем файле kcore_strings.
$ grep "root@manhunt" kcore_strings $ grep "]#" kcore_strings 11921096 [root@manhunt]# 16643784 [root@manhunt root]# 30692969 ]#]#Мы видим смещения некоторых строк относительно начала файла. Следующим шагом будет открытие файла в текстовом редакторе и переход на позицию файла, находящеюся рядом с полученными смещениями. Если нам повезет, мы найдем и другие команды, выполненные в прошлом. Но мы должны помнить, что страницы виртуальной памяти в физическую память и раздел подкачки пишутся неорганизованно, поэтому наши выводы могут быть полностью не верными.
$ less kcore_strings /11921096 11921096 [root@manhunt]# 11921192 /usr/bin/perl 11921288 perl apache_mod_exploit.pl ...Пример выше показывает несколько команд исполненных на скомпрометированной системе.
$ grep -e "\/proc\/" -e "\/bin\/" -e "\/bin\/.*?sh" kcore_strings $ grep -e "ftp" -e "root" kcore_strings $ grep -e "rm -" kcore_strings $ grep -e ".tgz" kcore_strings
$ grep -e "[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+" kcore_strings $ grep -e "\.pl" kcore_strings2.7 Подробный анализ вторжения
В этом заключительном разделе я покажу, как нужно изучать файл kcore с помощью gdb. Вначале я опишу, как проверить правильность адресов системных вызовов в таблице системных вызовов. Изменения адреса системного вызова - самый простой путь установить lkm rootkit на скомпрометированную систему. В следующем примере я покажу, как просмотреть все процессы, который были запущенные на недавно скомпрометированной системе. Результаты этих примеров могут быть сравнены с результатами, полученными в шагах 7 и 8.
Для начала анализа нам нужна следующая информация:
- образ памяти в формате ELF (см. Шаг 6)
- скомпилированный образ ядра (мы должны примонтировать образы файловых систем и скопировать файл vmlinux-* из директории /boot)
- таблицы экспорта (System.map находится в директории /boot скомпрометированной системы, также мы можем использовать файл ksyms - см. Шаг 7)
- исходные коды ядра, используемого на скомпрометированной машине (если исходные коды не доступны, мы должны загрузить необходимую версию со следующего веб сайта: www.kernel.org)
Затем запустим gdb:
#gdb vmlinux kcore GNU gdb Red Hat Linux (5.1.90CVS-5) Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux"... warning: core file may not match specified executable file. Core was generated by `ro root=/dev/sda2 hdc=ide-scsi'. #0 0x00000000 in ?? () (gdb)Теперь мы готовы начать наши исследования.
Пример 1: Проверка адресов системных вызовов.
Вначале нам нужно найти адрес таблицы системных вызовов (sys_call_table). Почти в каждой Linux системе эта информация экспортируется и может быть найдена в файле Symbol.map .
# cat Symbol.map | grep sys_call_table c02c209c D sys_call_tableТеперь мы можем просматривать элементы таблицы системных вызовов. Каждый элемент это адрес системного вызова. Чтобы правильно их интерпретировать, рекомендую посмотреть файл entry.S из исходников ядра скомпрометированной системы. В этом файле находится упорядоченный список системных вызовов.
ENTRY(sys_call_table)
.long SYMBOL_NAME(sys_ni_syscall) /* 0 - old "setup()" system call*/
.long SYMBOL_NAME(sys_exit)
.long SYMBOL_NAME(sys_fork)
.long SYMBOL_NAME(sys_read)
.long SYMBOL_NAME(sys_write)
.long SYMBOL_NAME(sys_open) /* 5 */
.long SYMBOL_NAME(sys_close)
...
Та же последовательность и в sys_call_table. Чтобы просмотреть первые 10 адресов системных вызовов сделаем следующее:
(gdb) x/10x 0xc02c209c 0xc02c209c <sys_call_table>: 0xc01217c0 0xc011ac50 0xc0107510 0xc0138d50 0xc02c20ac <sys_call_table+16>: 0xc0138e50 0xc0138880 0xc01389b0 0xc011b010 0xc02c20bc <sys_call_table+32>: 0xc0138930 0xc01445c0Адрес 0xc01217c0 - это системный вызов sys_ni_call, адрес 0xc011ac50 - системный вызов sys_exit, и т.д.
Если мы доверяем адресам из System.map или ksyms, мы можем сравнить почти каждый адрес. Конечно, злоумышленники изменяют адрес не каждого системного вызова, есть несколько популярных, таких как sys_read, sys_getdents или sys_write.
Пример 2: Список активных процессов.
Чтобы получить полный список запущенных процессов, мы должны узнать адрес структуры init_task_union. Эта структура указывает на первый дескриптор процесса, который называется "process 0" или "swapper".
# cat Symbol.map | grep init_task_union c02da000 D init_task_unionТеперь нам нужно узнать, как выглядит эта структура. Пример структуры init_task есть в заголовочном файле sched.h в исходниках ядра скомпрометированной системы. Структура task_struct также находится в этом файле.
#define INIT_TASK(tsk)
{
state: 0,
flags: 0,
sigpending: 0,
addr_limit: KERNEL_DS,
exec_domain: &default_exec_domain,
...
run_list: LIST_HEAD_INIT(tsk.run_list),
time_slice: HZ,
next_task: &tsk,
prev_task: &tsk,
p_opptr: &tsk,
...
Самые важные для нас поля - prev_task и next_task каждого дескриптора процесса (тип task_struct). Они помогут нам создать список активных процессов. Поле next_task это указатель на дескриптор следующего процесса, prev_task - указатель на дескриптор предыдущего процесса.
В примере ниже, я выведу список фрагментов дескриптора процесса (тип task_struct), который находится по адресу 0xc514c000.
(gdb) x/180x 0xc514c000 ... 0xc514c040: 0x00000000 0xffffffff 0x00000004 0xc1be0000 0xc514c050: 0xc4dac000 0xc5ea8e40 0xc5ea8e40 0xc02c56d4 ... 0xc514c070: 0x00000001 0x00001ace 0x00001ace 0x00000000 ... 0xc514c230: 0xffffffff 0xffffffff 0x61620000 0x00006873 0xc514c240: 0x00007974 0x00000000 0x00000000 0x00000000 ...где:
0xc1be0000 - next_task
0xc4dac000 - prev_task
0x00001ace - идентификатор процесса = 6268 в десятеричной системе счисления
0x61620000 и 0x00006873 - имя процесса (comm таблица в структуре) = "bash" в данном случае
Чтобы вывести дескриптор следующего процесса (тип task_struct), сделаем так:
(gdb) x/180x 0xc1be0000Получая информацию о каждом дескрипторе процесса, мы можем узнать полный список процессов.
3. Резюме
Цель этой статьи показать некоторые методы, используемые для сбора информации, простого и углубленного анализа данных. В частности, я сосредоточился на тех данных, которые были бы полностью потеряны после выключения системы. Эта часть анализа инцидента и процесса реагирования на инцидент очень сложна в реализации; в течение всех этапов мы должны быть очень осторожными и документировать каждое действие. Мы также посмотрели на некоторые преимущества и недостатки программного обеспечения для сбора информации с работающей системы. И наконец, несмотря на то, что я показал, как выглядит процедура сбора информации в Linux, мы также можем выполнить почти такие же шаги на других операционных системах. В следующей статье вы узнаете, как получить необходимую информацию с работающей Windows системы.
Ссылки