Переполнение буфера стека

редактировать

В программном обеспечении переполнение буфера стека или переполнение буфера стека происходит, когда программа записывает в адрес памяти в стеке вызовов программы вне предполагаемой структуры данных, которая обычно представляет собой буфер фиксированной длины. Ошибки переполнения буфера стека возникают, когда программа записывает в буфер, расположенный в стеке, больше данных, чем фактически выделено для этого буфера. Это почти всегда приводит к повреждению смежных данных в стеке, а в случаях, когда переполнение было инициировано по ошибке, часто приводит к сбою или неправильной работе программы. Переполнение буфера стека - это тип более общей ошибки программирования, известной как переполнение буфера (или переполнение буфера). Переполнение буфера в стеке с большей вероятностью нарушит выполнение программы, чем переполнение буфера в куче, потому что стек содержит адреса возврата для всех активных вызовов функций.

Переполнение буфера стека может быть вызвано намеренно как часть атаки, известной как разбивание стека . Если уязвимая программа работает с особыми привилегиями или принимает данные от ненадежных сетевых узлов (например, веб-сервера ), то ошибка является потенциальной уязвимостью безопасности. Если буфер стека заполнен данными, полученными от ненадежного пользователя, то этот пользователь может повредить стек таким образом, чтобы внедрить исполняемый код в запущенную программу и взять под контроль процесс. Это один из самых старых и надежных методов для злоумышленников получить несанкционированный доступ к компьютеру.

Содержание
  • 1 Использование переполнения буфера стека
  • 2 Различия, связанные с платформой
    • 2.1 Растущие стеки
  • 3 Схемы защиты
    • 3.1 Канарейки стека
    • 3.2 Неисполняемый стек
    • 3.3 Рандомизация
  • 4 Примечательные примеры
  • 5 См. Также
  • 6 Ссылки
Использование буфера стека overflows

Канонический метод использования переполнения буфера на основе стека - перезапись адреса возврата функции указателем на данные, контролируемые злоумышленником (обычно в самом стеке). Это проиллюстрировано с помощью strcpy() в следующем примере:

#include void foo (char * bar) {char c [12]; strcpy (c, bar); // без проверки границ} int main (int argc, char ** argv) {foo (argv [1]); возврат 0; }

Этот код берет аргумент из командной строки и копирует его в локальную переменную стека c. Это отлично работает для аргументов командной строки меньше 12 символов (как вы можете видеть на рисунке B ниже). Любые аргументы длиной более 11 символов приведут к повреждению стека. (Максимальное количество безопасных символов на единицу меньше размера буфера здесь, потому что в языке программирования C строки заканчиваются нулевым байтовым символом. Таким образом, для ввода из двенадцати символов требуется тринадцать байтов для хранения, за вводом следует нулевым байтом контрольного значения. Нулевой байт затем завершается перезаписью области памяти, которая на один байт выходит за пределы буфера.)

Программный стек в foo ()с различными входами:

А. - Перед копированием данных. B. - «привет» - это первый аргумент командной строки. C. - "A A A A A A A A A A A A A A A A A A A A \ x08 \ x35 \ xC0 \ X80 "- первый аргумент командной строки.

Обратите внимание, что на рисунке C выше, когда в командной строке указан аргумент размером более 11 байт foo ()перезаписывает данные локального стека, сохраненный кадр указатель, а главное - адрес возврата. Когда foo ()возвращает, он выталкивает адрес возврата из стека и переходит к этому адресу (т.е. начинает выполнять инструкции с этого адреса). Таким образом, злоумышленник перезаписал адрес возврата указателем на буфер стека char c [12], который теперь содержит данные, предоставленные злоумышленником. При фактическом использовании переполнения буфера стека строка "A" вместо этого будет шеллкодом, подходящим для платформы и желаемой функции. Если у этой программы были особые привилегии (например, бит SUID установлен для запуска от имени суперпользователя ), то злоумышленник мог использовать эту уязвимость, чтобы получить привилегии суперпользователя на уязвимой машине.

Злоумышленник также может изменить значения внутренних переменных, чтобы воспользоваться некоторыми ошибками. В этом примере:

#include #include void foo (char * bar) {float My_Float = 10.5; // Адрес = 0x0023FF4C char c [28]; // Addr = 0x0023FF30 // Напечатает 10.500000 printf ("My Float value =% f \ n", My_Float); / * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~ Карта памяти: @: c выделенная память #: выделенная память My_Float * c * My_Float 0x0023FF30 0x0023FF4C | | @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.com memcpy поместит 0x1010C042 (little endian) в значение My_Float. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~ * / memcpy (c, bar, strlen (bar)); // без проверки границ... // напечатает 96.031372 printf ("My Float value =% f \ n", My_Float); } int main (int argc, char ** argv) {foo ("моя строка слишком длинная !!!!! \ x10 \ x10 \ xc0 \ x42"); возврат 0; }
Различия, связанные с платформой

У ряда платформ есть тонкие различия в их реализации стека вызовов, которые могут повлиять на способ работы эксплойта переполнения буфера стека. Некоторые машинные архитектуры хранят адрес возврата верхнего уровня стека вызовов в регистре. Это означает, что любой перезаписанный адрес возврата не будет использоваться до последующего раскручивания стека вызовов. Другой пример специфической для машины детали, которая может повлиять на выбор методов эксплуатации, - это тот факт, что большинство архитектур машин в стиле RISC не допускают невыровненный доступ к памяти. В сочетании с фиксированной длиной для машинных кодов операций это машинное ограничение может сделать переход к технике ESP практически невозможным (за одним исключением, когда программа фактически содержит маловероятный код для явного перехода к регистру стека).

Растущие стеки

В рамках темы переполнения буфера стека часто обсуждается, но редко встречается архитектура, в которой стек растет в противоположном направлении. Это изменение в архитектуре часто предлагается в качестве решения проблемы переполнения буфера стека, поскольку любое переполнение буфера стека, происходящее в том же кадре стека, не может перезаписать указатель возврата. Дальнейшее расследование заявленной защиты показало, что это в лучшем случае наивное решение. Любое переполнение, происходящее в буфере из предыдущего кадра стека, по-прежнему будет перезаписывать указатель возврата и допускать злонамеренное использование ошибки. Например, в приведенном выше примере указатель возврата для fooне будет перезаписан, потому что переполнение фактически происходит внутри фрейма стека для memcpy. Однако, поскольку буфер, который переполняется во время вызова memcpy, находится в предыдущем фрейме стека, указатель возврата для memcpyбудет иметь численно больший адрес памяти, чем буфер. Это означает, что вместо перезаписи указателя возврата для fooбудет перезаписан указатель возврата для memcpy. В лучшем случае это означает, что рост стека в противоположном направлении изменит некоторые детали того, как можно использовать переполнение буфера стека, но это не приведет к значительному сокращению количества уязвимых ошибок.

Схемы защиты

За прошедшие годы был разработан ряд схем целостности потока управления для предотвращения злонамеренной эксплуатации переполнения буфера стека. Обычно их можно разделить на три категории:

  • Обнаружение переполнения буфера стека и, таким образом, предотвращение перенаправления указателя инструкции на вредоносный код.
  • Предотвращение выполнения вредоносного кода из стека без прямого обнаружения переполнение буфера стека.
  • Произвести случайное изменение пространства памяти, чтобы поиск исполняемого кода стал ненадежным.

Стек канареек

Стек канареек, названный по аналогии с канарейкой в ​​угле mine, используются для обнаружения переполнения буфера стека до того, как может произойти выполнение вредоносного кода. Этот метод работает путем помещения небольшого целого числа, значение которого выбирается случайным образом при запуске программы, в памяти непосредственно перед указателем возврата стека. Большинство переполнений буфера перезаписывают память с более низких адресов памяти на более высокие, поэтому, чтобы перезаписать указатель возврата (и, таким образом, получить контроль над процессом), необходимо перезаписать канареечное значение. Это значение проверяется, чтобы убедиться, что оно не изменилось, прежде чем процедура использует указатель возврата в стеке. Этот метод может значительно увеличить сложность использования переполнения буфера стека, поскольку он заставляет злоумышленника получить контроль над указателем инструкции некоторыми нетрадиционными способами, такими как повреждение других важных переменных в стеке.

Невыполняемый стек

Другой подход к предотвращению эксплуатации переполнения буфера стека заключается в применении политики памяти в области памяти стека, которая запрещает выполнение из стека (W ^ X, «Запись XOR Execute»). Это означает, что для выполнения шелл-кода из стека злоумышленник должен либо найти способ отключить защиту выполнения из памяти, либо найти способ разместить полезную нагрузку шелл-кода в незащищенной области памяти. Этот метод становится все более популярным сейчас, когда аппаратная поддержка флага отсутствия выполнения доступна в большинстве процессоров для настольных ПК.

Хотя этот метод определенно приводит к провалу канонического подхода к эксплуатации переполнения буфера стека, он не лишен проблем. Во-первых, обычно находят способы хранить шелл-код в незащищенных областях памяти, таких как куча, и поэтому очень мало нужно менять способ эксплуатации.

Даже если бы это было не так, есть другие способы. Самым ужасным является так называемый метод return to libc для создания шелл-кода. В этой атаке вредоносная полезная нагрузка будет загружать стек не с шелл-кодом, а с правильным стеком вызовов, так что выполнение будет направлено на цепочку стандартных вызовов библиотеки, обычно с эффектом отключения защиты памяти от выполнения и разрешения шелл-коду работать в обычном режиме. Это работает, потому что выполнение никогда не переносится в сам стек.

Вариантом return-to-libc является возвратно-ориентированное программирование (ROP), которое устанавливает серию адресов возврата, каждый из которых выполняет небольшую последовательность выбранной машиной инструкции в рамках существующего программного кода или системных библиотек, последовательность которых заканчивается возвратом. Каждый из этих так называемых гаджетов выполняет простую манипуляцию с регистрами или аналогичное выполнение перед возвратом, и объединение их вместе достигает целей злоумышленника. Можно даже использовать "безвозвратное" программирование, ориентированное на возврат, используя инструкции или группы инструкций, которые ведут себя так же, как инструкция возврата.

Рандомизация

Вместо отделения кода от данных, другой метод смягчения последствий - введение рандомизации в область памяти исполняемой программы. Поскольку злоумышленнику необходимо определить, где находится исполняемый код, который можно использовать, либо предоставляется исполняемая полезная нагрузка (с исполняемым стеком), либо она создается с использованием повторного использования кода, например, в ret2libc или в программировании, ориентированном на возврат (ROP). Рандомизация структуры памяти, как концепция, не позволяет злоумышленнику узнать, где находится какой-либо код. Однако реализации обычно не все рандомизируют; обычно сам исполняемый файл загружается по фиксированному адресу, и, следовательно, даже когда ASLR (рандомизация разметки адресного пространства) комбинируется с невыполняемым стеком, злоумышленник может использовать эту фиксированную область памяти. Следовательно, все программы должны быть скомпилированы с использованием PIE (независимые от позиции исполняемые файлы), чтобы даже эта область памяти была рандомизирована. Энтропия рандомизации отличается от реализации к реализации, и достаточно низкая энтропия сама по себе может быть проблемой с точки зрения грубого форсирования пространства памяти, которое рандомизируется.

Примечательные примеры
  • Червь Morris в 1988 г. частично распространился за счет использования переполнения буфера стека на сервере Unix finger. [1]
  • Червь Witty в 2004 г. распространился, эксплуатируя переполнение буфера стека в Internet Security Systems BlackICE Desktop Agent. [2]
  • Червь Slammer в 2003 г. распространился, эксплуатируя переполнение буфера стека на SQL-сервере Microsoft. [3]
  • Червь Blaster в Распространение 2003 г. за счет использования переполнения буфера стека в службе Microsoft DCOM.
  • Существует несколько примеров Wii, позволяющих запускать произвольный код в неизмененной системе.. «Сумеречный хак», который включает в себя длинное имя лошади главного героя в The Legend of Zelda: Twilight Princess и «Smash Stack» для Super Smash Bros. Brawl, который включает использование SD-карты для загрузки специально подготовленного файла в редактор игровых уровней. Хотя оба могут использоваться для выполнения любого произвольного кода, последний часто используется для простой перезагрузки самой Brawl с применением модификаций.
См. Также
Ссылки
Последняя правка сделана 2021-06-09 07:01:50
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте