Переполнение буфера стека - Stack buffer overflow

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

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

Использование переполнения буфера стека

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

#включают <string.h>пустота фу(char *бар){   char c[12];   strcpy(c, бар);  // без проверки границ}int главный(int argc, char **argv){   фу(argv[1]);   возвращаться 0;}

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

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

A. - Перед копированием данных.
Б. - «привет» - это первый аргумент командной строки.
C. - «A 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 () возвращает он выталкивает адрес возврата из стека и переходит на этот адрес (т.е. начинает выполнение инструкций с этого адреса). Таким образом, злоумышленник перезаписал адрес возврата указателем на буфер стека. символ c [12], который теперь содержит данные, предоставленные злоумышленником. При фактическом использовании переполнения буфера стека строка "A" вместо этого будет шеллкод подходит для платформы и желаемой функции. Если у этой программы были особые привилегии (например, SUID бит установлен для работы как суперпользователь ), то злоумышленник может использовать эту уязвимость для получения привилегий суперпользователя на уязвимой машине.[3]

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

#включают <string.h>#включают <stdio.h>пустота фу(char *бар){   плавать My_Float = 10.5; // Адрес = 0x0023FF4C   char  c[28];           // Адрес = 0x0023FF30   // Напечатаем 10.500000   printf("Мое значение с плавающей запятой =% f", My_Float);    /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~       Карта памяти:       @: c выделенная память       #: My_Float выделенная память           * c * My_Float       0x0023FF30 0x0023FF4C           |                           |           @@@@@@@@@@@@@@@@@@@@@@@@@@@@#####      foo ("моя строка слишком длинная !!!!! XXXXX");   memcpy поместит 0x1010C042 (little endian) в значение My_Float.   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/   memcpy(c, бар, Strlen(бар));  // без проверки границ ...   // Напечатаем 96.031372   printf("Мое значение с плавающей запятой =% f", My_Float);}int главный(int argc, char **argv){   фу("моя строка слишком длинная !!!!! x10x10xc0x42");   возвращаться 0;}

Различия, связанные с платформой

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

Стеки, которые растут

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

Схемы защиты

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

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

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

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

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

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

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

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

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

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

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

Известные примеры

  • В Червь Морриса в 1988 году распространение частично за счет использования переполнения буфера стека в Unix Палец сервер.[1]
  • В Остроумный червь в 2004 году распространение за счет использования переполнения буфера стека в Системы Интернет-безопасности Агент BlackICE Desktop Agent.[2]
  • В Slammer червь в 2003 году распространение за счет использования переполнения буфера стека в Microsoft SQL-сервер.[3]
  • В Бластерный червь в 2003 году распространение за счет использования переполнения буфера стека в Microsoft DCOM служба.
  • Есть несколько примеров Wii позволяет запускать произвольный код в немодифицированной системе. «Сумеречный хакер», который включает в себя длинное имя лошади главного героя в Легенда о Зельде: Сумеречная принцесса,[14] и "Smash Stack" для Драка Super Smash Bros. который предполагает использование SD-карты для загрузки специально подготовленного файла в редактор игровых уровней. Хотя оба могут использоваться для выполнения любого произвольного кода, последний часто используется для простой перезагрузки Драка сам с модификации применяемый.[15]

Смотрите также

Рекомендации

  1. ^ а б Фитен, Уильям Л .; Сикорд, Роберт (27 марта 2007 г.). «ВТ-МБ. Нарушение границ памяти». США CERT.
  2. ^ а б c Дауд, Марк; Макдональд, Джон; Шу, Джастин (ноябрь 2006 г.). Искусство оценки безопасности программного обеспечения. Эддисон Уэсли. С. 169–196. ISBN  0-321-44442-6.
  3. ^ а б c Леви, Элиас (1996-11-08). «Разбить стопку ради удовольствия и прибыли». Phrack. 7 (49): 14.
  4. ^ Pincus, J .; Бейкер, Б. (июль – август 2004 г.). "Beyond Stack Smashing: последние достижения в использовании переполнения буфера" (PDF). Журнал IEEE Security and Privacy Magazine. 2 (4): 20–27. Дои:10.1109 / MSP.2004.36.
  5. ^ Буребиста. "Переполнение стека" (PDF). Архивировано из оригинал (PDF) 28 сентября 2007 г. (мертвая ссылка)
  6. ^ Бертран, Луи (2002). «OpenBSD: исправьте ошибки, защитите систему». MUSESS '02: Симпозиум по разработке программного обеспечения Университета Макмастера. Архивировано из оригинал 30 сентября 2007 г.
  7. ^ пр1. «Использование уязвимостей SPARC Buffer Overflow». Цитировать журнал требует | журнал = (помощь)
  8. ^ Любопытно (2005-01-08). «Обратный инжиниринг - взлом PowerPC в Mac OS X с помощью GDB». Phrack. 11 (63): 16.
  9. ^ Соварел, Ана Нора; Эванс, Дэвид; Павел, Нафанаил. «Где FEEB? Эффективность рандомизации набора инструкций». Цитировать журнал требует | журнал = (помощь)
  10. ^ Жодиак (28.12.2001). «Переполнение HP-UX (PA-RISC 1.1)». Phrack. 11 (58): 11.
  11. ^ Фостер, Джеймс С.; Осипов, Виталий; Бхалла, Ниш; Хайнен, Нильс (2005). Атаки переполнения буфера: обнаружение, использование, предотвращение (PDF). Соединенные Штаты Америки: Syngress Publishing, Inc. ISBN  1-932266-67-4.
  12. ^ Нергал (28 декабря 2001). «Расширенные эксплойты return-into-lib (c): пример использования PaX». Phrack. 11 (58): 4.
  13. ^ Checkoway, S .; Davi, L .; Дмитриенко, А .; Садеги, А. Р .; Shacham, H .; Винанди, М. (октябрь 2010 г.). «Обратно-ориентированное программирование без возврата». Материалы 17-й конференции ACM по компьютерной и коммуникационной безопасности - CCS '10. С. 559–572. Дои:10.1145/1866307.1866370. ISBN  978-1-4503-0245-6.
  14. ^ «Сумеречный взлом - WiiBrew». wiibrew.org. Получено 2018-01-18.
  15. ^ "Smash Stack - WiiBrew". wiibrew.org. Получено 2018-01-18.