Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

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

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

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

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

#include  <string.h>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()с различными входами:

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

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

#include  <string.h>#include  <stdio.h>void  foo ( char  * bar ) {  float  My_Float  =  10,5 ;  // Адрес = 0x0023FF4C  char  c [ 28 ];  // Адрес = 0x0023FF30 // Напечатает 10.500000  printf ( "My Float value =% f \ n " ,  My_Float ); / * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~  Карта памяти:  @: c выделенная память  #: выделенная память My_Float * c * My_Float  0x0023FF30 0x0023FF4C  | |  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#####  foo ("моя строка слишком длинная !!!!! XXXXX"); 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 не допускают невыровненный доступ к памяти. [7]В сочетании с фиксированной длиной для машинных кодов операций это машинное ограничение может сделать переход к технике ESP практически невозможным (за одним исключением, когда программа фактически содержит маловероятный код для явного перехода к регистру стека). [8] [9]

Стеки, которые растут [ править ]

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

Схемы защиты [ править ]

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

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

Стек канареек [ править ]

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

Невыполняемый стек [ править ]

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

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

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

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

Рандомизация [ править ]

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

Известные примеры [ править ]

  • Моррис червя в 1988 году распространения частично за счет использования переполнение стека буфера в Unix пальца сервера. [1]
  • В 2004 году червь Witty распространился путем использования переполнения буфера стека в Internet Security Systems BlackICE Desktop Agent. [2]
  • В 2003 году червь Slammer распространился, эксплуатируя переполнение буфера стека в SQL-сервере Microsoft . [3]
  • Blaster червь в 2003 году распространение эксплуатируя переполнение стека буфера в Microsoft DCOM службы.
  • Есть несколько примеров Wii, позволяющих запускать произвольный код в немодифицированной системе. «Сумерки хак» , который включает в себя давая длинное имя для лошади главного героя в Легенда о Zelda: Twilight Princess , [14] и «Smash Stack» для Super Smash Bros. Brawl , который включает использование SD карт для загрузки специально подготовленного файл в редактор уровней игры. Хотя оба могут использоваться для выполнения любого произвольного кода, последний часто используется для простой перезагрузки самой Brawl с внесенными изменениями . [15]

См. Также [ править ]

  • Рандомизация разметки адресного пространства
  • Переполнение буфера
  • Стек вызовов
  • Компьютерная безопасность
  • ExecShield
  • Исполняемая защита пространства
  • Эксплойт (компьютерная безопасность)
  • Атака на форматную строку
  • Переполнение кучи
  • Целочисленное переполнение
  • Бит NX
  • PaX
  • Возвратно-ориентированное программирование
  • Linux с повышенной безопасностью
  • Переполнение стека
  • Нарушение хранилища
  • Уязвимость (вычисления)

Ссылки [ править ]

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