В прошлом месяце, в первой части этой серии статей мы обсуждали подготовку и основные этапы, которые должны быть приняты во внимания при анализе работающей Linux системы, которая была скомпрометирована. Теперь мы продолжим наш анализ в поисках злонамеренного кода на работающей системе, а затем обсудим некоторые способы анализа данных, которые могут быть применены сразу же, как только данные будут переданы на наш удаленный хост.
Мариус Бурдах, перевод Владимир Куксенок1. Введение
(compromised)# /mnt/cdrom/bashМы должны помнить, что почти любой командный интерпретатор пишет каждую напечатанную команду в файл истории. Так как мы не хотим изменить локальную файловую системы, лучшим решением будет выключить запись истории команд.
(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). Копируя подозрительный процесс, какие-то страницы мы заставим считаться из файла подкачки в память, а какие-то записаться в него. Другая важная особенность состоит в том, что мы копируем только ту память, в которой размещены данные процесса.
(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 заменяет подпрограмму обработчик, осуществляющий вывод листинга директории.
(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 мы видим, что программа, которая создала процесс, была удалена злоумышленником, у нас еще есть шансы восстановить ее. Я покажу, как сделать это в следующем разделе.
(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Кроме этого, мы можем сделать копии лишь некоторых выборочных данных процесса. Более подробная информация об этом в следующем разделе.
Команда | Описание |
/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) |
(remote)#nc -l -p port > end_time (compromised)# /mnt/cdrom/date | /mnt/cdrom/nc (remote) portТеперь мы достигли того момента, когда можем выключить скомпрометированную систему. Запомните, что для завершения работы системы нельзя использовать какие-либо стандартные команды. Мы должны выдернуть силовой кабель из системного блока или источника бесперебойного питания.
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 и блоков данных.
$ strings -t d kcore > kcore_strings $ md5sum kcore_strings > kcore_strings.md5Утилита grep и регулярные выражения важны на этом начальном этапе анализа. Через несколько минут мы сможем найти доказательства вторжения. Мы должны подумать, какие данные мы собираемся искать, - например, мы ищем команды, напечатанные злоумышленником, IP адреса, пароли или даже расшифрованную часть злонамеренного кода?
$ 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 Подробный анализ вторжения
#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)Теперь мы готовы начать наши исследования.
# 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, и т.д.
# 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 - указатель на дескриптор предыдущего процесса.
(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 ...где:
(gdb) x/180x 0xc1be0000Получая информацию о каждом дескрипторе процесса, мы можем узнать полный список процессов.
Разбираем кейсы, делимся опытом, учимся на чужих ошибках