stringtranslate.com

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

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

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

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

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

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

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

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

На рисунке C выше, когда в командной строке передается аргумент размером более 11 байт, foo()перезаписываются данные локального стека, сохраненный указатель кадра и, что наиболее важно, адрес возврата. При foo()возврате он извлекает адрес возврата из стека и переходит к этому адресу (т. е. начинает выполнять инструкции с этого адреса). Таким образом, злоумышленник перезаписал адрес возврата указателем на буфер стека char c[12], который теперь содержит данные, предоставленные злоумышленником. В реальном случае использования переполнения буфера стека строка «А» вместо этого будет шелл-кодом , подходящим для платформы и желаемой функции. Если бы эта программа имела особые привилегии (например, бит 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 (с прямым порядком байтов) в значение 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] В сочетании с фиксированной длиной машинных кодов операций это машинное ограничение может сделать технику перехода в стек практически невозможной для реализации (за одним исключением, когда программа фактически содержит маловероятный код для явного перехода к регистру стека). . [8] [9]

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

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

Схемы защиты

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

Стек канарейок

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

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

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

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

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

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

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

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

Обход контрмер

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

Канареечный обход стека

Утечка информации с использованием уязвимости форматной строки

Злоумышленник может воспользоваться уязвимостью строки формата для раскрытия ячеек памяти уязвимой программы. [14]

Обход неисполняемого стека

Когда функция предотвращения выполнения данных включена и запрещает любой доступ к стеку на выполнение, злоумышленник все равно может использовать перезаписанный обратный адрес (указатель инструкции) для указания на данные в сегменте кода ( .text в Linux) или любом другом исполняемом разделе стека. программа. Цель состоит в том, чтобы повторно использовать существующий код. [15]

Роп-цепь

Состоит из небольшой перезаписи указателя возврата перед инструкцией возврата (ret в x86) программы. Инструкции между новым указателем возврата и инструкцией возврата будут выполнены, и инструкция возврата вернется к полезной нагрузке, контролируемой эксплуататором. [15] [ нужны разъяснения ]

Сеть Джоп

Переходно-ориентированное программирование — это метод, который использует инструкции перехода для повторного использования кода вместо инструкции ret. [16]

Обход рандомизации

Ограничением реализации ASLR в 64-битных системах является то, что она уязвима для атак раскрытия памяти и утечки информации. Злоумышленник может запустить ROP, раскрыв одиночный адрес функции, используя атаку утечки информации. В следующем разделе описывается аналогичная существующая стратегия взлома защиты ASLR. [17]

Яркие примеры

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

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

  1. ^ аб Фитен, Уильям Л.; Сикорд, Роберт (27 марта 2007 г.). «ВТ-МБ. Нарушение границ памяти». СЕРТ США .
  2. ^ abc Дауд, Марк; Макдональд, Джон; Шу, Джастин (ноябрь 2006 г.). Искусство оценки безопасности программного обеспечения . Эддисон Уэсли . стр. 169–196. ISBN 0-321-44442-6.
  3. ^ abc Леви, Элиас (1996-11-08). «Разбиваем стопку ради удовольствия и прибыли». Фрак . 7 (49): 14.
  4. ^ Пинкус, Дж.; Бейкер, Б. (июль – август 2004 г.). «Не только разрушение стека: последние достижения в использовании переполнения буфера» (PDF) . Журнал IEEE по безопасности и конфиденциальности . 2 (4): 20–27. дои :10.1109/MSP.2004.36. S2CID  6647392.
  5. ^ Буребиста. «Переполнение стека» (PDF) . Архивировано из оригинала (PDF) 28 сентября 2007 г.[ мертвая ссылка ]
  6. ^ Бертран, Луи (2002). «OpenBSD: исправьте ошибки, защитите систему». MUSESS '02: Симпозиум по разработке программного обеспечения Университета Макмастера . Архивировано из оригинала 30 сентября 2007 г.
  7. ^ пр1. «Использование уязвимостей переполнения буфера SPARC».{{cite web}}: CS1 maint: числовые имена: список авторов ( ссылка )
  8. ^ Любопытно (08 января 2005 г.). «Реверс-инжиниринг — взлом PowerPC в Mac OS X с помощью GDB». Фрак . 11 (63): 16.
  9. ^ Соварел, Ана Нора; Эванс, Дэвид; Пол, Нафанаил. Где ФЕБ? Эффективность рандомизации набора команд (отчет).
  10. ^ Жодиак (28 декабря 2001 г.). «Переполнение HP-UX (PA-RISC 1.1)». Фрак . 11 (58): 11.
  11. ^ Фостер, Джеймс С.; Осипов, Виталий; Бхалла, Ниш; Хайнен, Нильс (2005). Атаки на переполнение буфера: обнаружение, использование, предотвращение (PDF) . Соединенные Штаты Америки: ISBN Syngress Publishing, Inc. 1-932266-67-4.
  12. ^ Нергал (28 декабря 2001 г.). «Продвинутые эксплойты возврата в lib (c): пример PaX». Фрак . 11 (58): 4.
  13. ^ Чековей, С.; Дави, Л.; Дмитриенко А.; Садеги, Арканзас; Шахам, Х.; Винэнди, М. (октябрь 2010 г.). «Возвратно-ориентированное программирование без возвратов». Материалы 17-й конференции ACM «Компьютерная и коммуникационная безопасность — CCS '10» . стр. 559–572. дои : 10.1145/1866307.1866370. ISBN 978-1-4503-0245-6. S2CID  207182734.
  14. ^ Батт, Мухаммад Ариф; Аджмал, Зарафшан; Хан, Зафар Икбал; Идрис, Мухаммед; Джавед, Ясир (январь 2022 г.). «Углубленный обзор методов обхода предотвращения переполнения буфера». Прикладные науки . 12 (26): 6702. дои : 10.3390/app12136702 . ISSN  2076-3417.
  15. ^ Аб Батт, Мухаммад Ариф; Аджмал, Зарафшан; Хан, Зафар Икбал; Идрис, Мухаммед; Джавед, Ясир (январь 2022 г.). «Углубленный обзор методов обхода предотвращения переполнения буфера». Прикладные науки . 12 (13): 12–13. дои : 10.3390/app12136702 . ISSN  2076-3417.
  16. ^ Sécurité matérielle des systèmes (на французском языке). 03.09.2022.
  17. ^ Батт, Мухаммад Ариф; Аджмал, Зарафшан; Хан, Зафар Икбал; Идрис, Мухаммед; Джавед, Ясир (январь 2022 г.). «Углубленный обзор методов обхода предотвращения переполнения буфера». Прикладные науки . 12 (16): 6702. дои : 10.3390/app12136702 . ISSN  2076-3417.
  18. ^ "Сумеречный взлом - WiiBrew" . Wiibrew.org . Проверено 18 января 2018 г.
  19. ^ "Smash Stack - WiiBrew" . Wiibrew.org . Проверено 18 января 2018 г.