Есть такая очень старая и вросшая в *nix с
корнями штука под названием сигналы. Идея этих примитивов очень проста:
реализовать программный аналог прерываний. Различные процессы могут
посылать сигналы друг другу и самим себе, зная process id (pid)
получателя. Процесс-получатель волен либо назначить
функцию-обработчик сигнала, которая будет автоматически вызываться
при его получении, либо игнорировать его с помощью специальной
маски, либо же довериться поведению по умолчанию. So far so
good.
Поведение по умолчанию при получении сигнала А что означают эти
успокаивающие слова? Уверен, не то, что вы ожидали. Вики говорит,
что обработчики 28 стандартных сигналов (существуют и другие!) по
умолчанию таковы: 2 игнорируются, 4 вызывают остановку процесса, 1
его продолжение, 11 его завершение, 10 его завершение с созданием
дампа памяти. Вот это уже интересно! Итак, дело обстоит следующим
образом: даже если ваша программа никак не упоминает сигналы в
исходном коде, на самом деле она их использует, причём весьма
драматичным образом.
С этого момента нам придётся копнуть поглубже. Кто и кому может
посылать сигналы? Вики говорит: Процесс (или пользователь из
оболочки) с эффективным UID, не равным 0 (UID суперпользователя),
может посылать сигналы только процессам с тем же UID. Итак,
если вы запускаете 100 программ, то любая из них может запросто
убить все эти 100 программ с помощью системного API, даже если все
программы (кроме убийцы) никак не упоминали сигналы в своём
исходном коде. Если же вы работали под учёткой root-а, то вообще не
важно, кто запустил те или иные процессы, всё равно их можно
запросто завершить. Узнать pid конкретного процесса и выполнить его
заказное убийство, разумеется, можно, но ещё проще убить всех кого
можно путём простого перебора pid-ов.
Погоди-погоди, не гони лошадей. Ты упоминал, что сигналы можно
обрабатывать и игнорировать! слышу я голос своего читателя. Что
скажет Вики? Для альтернативной обработки всех сигналов, за
исключением SIGKILL и SIGSTOP, процесс может назначить свой
обработчик или игнорировать их возникновение модификацией своей
сигнальной маски. Смотрим на действия по умолчанию при
получении этих сигналов и видим: Завершение процесса, Остановка
процесса. Получается, что эти два действия мы можем сделать всегда,
когда посылка сигналов SIGKILL и SIGSTOP жертве в принципе
возможна. Единственное исключение процесс с pid 1 (init),
который имеет право игнорировать или обрабатывать любые сигналы,
включая KILL и STOP. Возможно, мы даже из-под root-а не сможем
убить один из главнейших системных процессов, но по-хорошему это
требует дополнительного исследования.
Что ж, картина стала чуть позитивнее, но она по-прежнему мрачна.
Если вы запускаете процесс, то он гарантированно может завершать и
останавливать кучу других процессов. При выполнении очень простого
условия разработчики процесса-получателя забыли проигнорировать или
как-то обработать один из многих других сигналов приложение-маньяк
сможет вызывать завершение процесса с созданием дампа памяти или
продолжение процесса после остановки. Если же разработчики
приложения-получателя повесили свои обработчики на какие-то
сигналы, можно попытаться помешать функционированию этого
приложения путём посылки ему сигналов. Последнее является темой для
отдельного разговора, потому что в силу асинхронности выполнения
обработчиков возможны гонки и неопределённое поведение
Абстрактные рассуждения это очень круто, но давай-ка ближе к
конкретике, скажет мне требовательный читатель. Окей, нет проблем!
Любому пользователю *nix хорошо знакома такая программа, как bash.
Эта программа развивается уже почти 30 лет и обладает целой горой
возможностей. Завалим-ка её для наглядности и получим из её памяти
какую-нибудь вкуснятину!
Я достаю из широких штанин свою домашнюю Ubuntu 16.04.2 и запускаю
на ней две копии bash 4.3.46. В одной из них я выполню
гипотетическую команду с секретными данными: export
password=SECRET. Давайте на время забудем про файл с историей
команд, в которую тоже записался бы пароль. Наберём в этом же окне
команду ps, чтобы узнать pid этого процесса скажем, 3580.
Не закрывая первое окно, перейдём во второе. Команда ps в нём даст
другой pid этого экземпляра bash скажем, 5378. Чисто для
наглядности именно из этого второго bash-а отправим сигнал первому
командой kill -SIGFPE 3580. Да, уважаемый читатель, это полный
абсурд: процесс 2 говорит никак не связанному с ним процессу 1, что
в этом самом процессе 1 произошла ошибочная арифметическая
операция. На экране появляется такое вот окошко:
Произошло желанное аварийное завершение с созданием дампа памяти,
то есть bash похоже не обрабатывает и не игнорирует этот сигнал.
Загуглив, где мне искать дамп, я нашёл развёрнутый ответ (раз, два). В моей Убунте дело обстоит так: если
приложение из стандартного пакета падает из-за сигнала, отличного
от SIGABRT, то дамп передаются программе Apport. Это как раз наш
случай! Данная программа компонует файл с диагностической
информацией и выдаёт окошко, показанное выше. Официальный сайт
гордо заявляет: Apport collects potentially sensitive data, such
as core dumps, stack traces, and log files. They can contain
passwords, credit card numbers, serial numbers, and other private
material. Так-так, интересно, а где там у нас лежит этот файл?
Ага, /var/crash/_bin_bash.1000.crash. Вытащим его содержимое в
папку somedir: apport-unpack /var/crash/_bin_bash.1000.crash
somedir. Помимо разных неинтересных мелочей там будет вожделенный
дамп памяти под названием CoreDump.
Вот он, момент истины! Давайте поищем в этом файле строку password
и посмотрим, что интересного мы получим в ответ. Команда strings
CoreDump | grep password напомнит забывчивому хакеру, что password
есть SECRET. Чудесно!
То же самое я проделал и со своим любимым текстовым редактором
gedit, начав набирать текст в буфере, а затем считав его уже из
дампа. Никаких проблем! В этот момент Вики предостерегающе шепнула
на ухо: Иногда (например, для программ, выполняемых от имени
суперпользователя) дамп памяти не создаётся из соображений
безопасности. Тааак, проверим При получении сигнала от рутового
bash-а второй рутовый bash упал с созданием дампа памяти, но из-за
прав доступа (-rw-r----- с владельцем root) прочитать его уже не
так просто, как прежние, владельцем которых был мой
пользовательский аккаунт. Что ж, коли гипотетической
программе-киллеру удалось послать сигнал с UID суперпользователя,
то и такой дамп она сможет потрогать.
Дотошный читатель может заметить: Тебе было очень легко найти
нужные данные в море мусора. Чистая правда, но я уверен: если вы
знаете, какую рыбу вы хотите поймать и где она плавает, то найти её
в сетях дампа должно быть реально почти всегда. Скажем, никто не
мешает скачать пакет с отладочной информацией для упавшей программы
и узнать содержимое интересующих вас переменных в GDB путём
post-mortem отладки.
Всё это может выглядеть вполне безобидно, но на самом деле таковым
не является. Все описанные мною действия могли быть запросто
проделаны программой или скриптом, работающей в пользовательском
режиме, не говоря уже о более привилегированном уровне доступа. В
сухом остатке получаем, что зловредная исполнимая штука может легко
рубить программы направо и налево, а часто ещё и свободно читать
всю их память. Вот тебе и сигналы да отчёты об ошибках! Уверен, что
на других *nix-платформах и с другими программами-получателями
ситуация аналогична, но проверять я это, конечно, не буду.
Может возникнуть возражение: зловредина может просто
воспользоваться средствами отладки для утягивания интересных данных
из приложения. Это действительно так. К чему же в таком случае этот
пост? Моя мысль такова: первое, что приходит на ум при попытке
пресечь кражу данных из приложений это как раз ограничения на
отладочные инструменты. Наверняка антивирусные инструменты
отлавливают использование ptrace() в первую очередь это очень
подозрительное событие. Сигналы же совсем другое дело. Один процесс
посылает другому стандартный сигнал ну и что? На первый взгляд, это
совершенно нормальное событие. Но, как мы уже видели, это может
привести к аварийному завершению приложения, созданию дампа ядра в
какой-то папке, из которой его можно будет попробовать утянуть.
Когда я попытался открыть страничку авторизации vk.com и свалить
Firefox тем же роковым сигналом, он упал, но вызвал свой обработчик
дампов. Дампы в хитром формате minidump сохраняются по адресу
~/.mozilla/firefox/Crash Reports/{pending или submitted} и требуют
дополнительного исследования. Вот что вы узнаете, если в окошке
настроек кликните на Learn more напротив галочки (текст ниже раньше
висел по адресу www.mozilla.org/ru/privacy/firefox/#crash-reporter):
При желании вы можете отправить сообщение об ошибке в корпорацию
Mozilla после падения браузера Firefox. Такое сообщение содержит
технические данные, которые мы используем для улучшения работы
Firefox, в том числе информацию о причине падения, об активном
URL-адресе на момент падения, а также о состоянии памяти компьютера
на момент падения. Сообщения об ошибках, которые мы получаем, могут
содержать персональную информацию. Некоторые части сообщений об
ошибках мы публикуем в открытом доступе по адресу crash-stats.mozilla.com. Перед публикацией сообщений
об ошибках мы принимаем меры для автоматического удаления
персональной информации. Мы не удаляем ничего из написанного вами в
полях для комментариев. В URL-ках редко бывает что-то
по-настоящему вкусное, а вот есть ли в дампах пароли или cookie,
вопрос хороший!
На этой таинственной и интригующей ноте я закончу. Ко мне пришёл
сигнал, который я забыл явно обработать.
P. S. Я написал простую программу с таким обработчиком сигнала
SIGUSR1: напечатать на экран строку 1, войти в бесконечный цикл. Я
надеялся, что если много раз посылать этой программе сигнал
SIGUSR1, то обработчик будет вызываться многократно, что вызовет
переполнение стека. К моему сожалению, обработчик вызывался лишь
один раз. Окей, напишем аналогичный обработчик сигнала SIGUSR2 и
будем посылать два разных сигнала в надежде, что это свалит жертву
Увы, но и это не помогло: каждый из обработчиков был вызван лишь
однажды. Переполняли-переполняли, да не выпереполняли!
P. S. 2. В мире Windows есть некое подобие сигналов сообщения, которые можно отправлять окнам.
Весьма вероятно, что их тоже можно использовать for fun and
profit!
Оригинал опубликован в моём блоге
5.05.17.