Защита от переполнения буфера

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

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

Как правило, защита от переполнения буфера изменяет организацию данных, выделенных стеком, так что оно включает значение canary, которое при уничтожении переполнением буфера стека показывает, что предшествующий ему буфер в памяти имеет был переполнен. Путем проверки канареечного значения выполнение затронутой программы может быть прекращено, что предотвратит ее неправильное поведение или позволит злоумышленнику получить контроль над ней. Другие методы защиты от переполнения буфера включают проверку границ, которая проверяет доступ к каждому выделенному блоку памяти, чтобы они не могли выходить за пределы фактически выделенного пространства, и тегирование, которое гарантирует, что память, выделенная для хранения данных, не может содержать исполняемый код.

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

Существует несколько реализаций защиты от переполнения буфера, в том числе для GNU Compiler Collection, LLVM, Microsoft Visual Studio и других компиляторов..

Содержание

  • 1 Обзор
  • 2 Канарейки
    • 2.1 Канарейки-терминаторы
    • 2.2 Случайные канарейки
    • 2.3 Случайные канарейки XOR
  • 3 Проверка границ
  • 4 Тэги
  • 5 Реализации
    • 5.1 GNU Compiler Collection (GCC)
    • 5.2 Microsoft Visual Studio
    • 5.3 IBM Compiler
    • 5.4 Clang / LLVM
    • 5.5 Intel Compiler
    • 5.6 Fail-Safe C
    • 5.7 StackGhost (аппаратное обеспечение -based)
  • 6 Пример канареечного
  • 7 См. также
  • 8 Ссылки
  • 9 Внешние ссылки

Обзор

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

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

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

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

Канарейки

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

. Терминология является отсылкой к исторической практике использования канареек на угольных шахтах, поскольку токсичные газы на них будут воздействовать раньше, чем на горняки, тем самым обеспечивая систему биологического предупреждения. Канарейки также известны как файлы cookie, которые предназначены для создания образа «сломанного файла cookie» при повреждении значения.

Используются три типа канареек: терминатор, случайный и случайный XOR. Текущие версии StackGuard поддерживают все три, а ProPolice поддерживает терминатор и случайные канарейки.

Канарейки-терминаторы

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

Случайные канарейки

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

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

Случайные канарейки XOR

Случайные канарейки XOR - это случайные канарейки, которые скремблируются с помощью XOR с использованием всех или части данных управления. Таким образом, после того, как канарейка или контрольные данные будут затерты, значение канарейки будет неправильным.

Случайные канарейки XOR имеют те же уязвимости, что и случайные канарейки, за исключением того, что метод «чтения из стека» получения канарейки немного сложнее. Злоумышленник должен получить канарейку, алгоритм и управляющие данные, чтобы повторно сгенерировать исходную канарейку, необходимую для подделки защиты.

Кроме того, случайные канарейки XOR могут защитить от определенного типа атак, связанных с переполнением буфера в структуре указателем, чтобы изменить указатель на часть управляющих данных. Из-за кодирования XOR канарейка будет ошибаться, если изменятся управляющие данные или возвращаемое значение. Из-за указателя управляющие данные или возвращаемое значение могут быть изменены без переполнения канарейки.

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

Проверка границ

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

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

Тегирование

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

Исторически тегирование использовалось для реализации языков программирования высокого уровня; при соответствующей поддержке со стороны операционной системы тегирование также может использоваться для обнаружения переполнения буфера. Примером может служить аппаратная функция NX bit, поддерживаемая процессорами Intel, AMD и ARM.

Реализации

Коллекция компиляторов GNU (GCC)

Защита от разрушения стека была впервые реализована StackGuard в 1997 году и опубликована на 1998 симпозиуме по безопасности USENIX. StackGuard был представлен как набор исправлений для серверной части Intel x86 GCC 2.7. StackGuard поддерживался для дистрибутива Linux Immunix с 1998 по 2003 год и был расширен реализациями для терминатора, случайных и случайных канареек XOR. StackGuard был предложен для включения в GCC 3.x на конференции GCC 2003 Summit Proceedings, но этого так и не произошло.

С 2001 по 2005 год IBM разработала исправления GCC для защиты от разрушения стека, известные как ProPolice. Он улучшил идею StackGuard, разместив буферы после локальных указателей и аргументов функций в кадре стека. Это помогло избежать повреждения указателей, предотвращая доступ к произвольным участкам памяти.

Инженеры Red Hat обнаружили проблемы с ProPolice, и в 2005 году повторно реализовали защиту от разбивания стека для включения в GCC 4.1. В этой работе был представлен флаг -fstack-protector, который защищает только некоторые уязвимые функции, и флаг -fstack-protector-all, который защищает все функции, независимо от того, нужны они им или нет.

В 2012 году инженеры Google внедрили флаг -fstack-protector-strong, чтобы добиться лучшего баланса между безопасностью и производительностью. Этот флаг защищает больше видов уязвимых функций, чем -fstack-protector, но не каждую функцию, обеспечивая лучшую производительность, чем -fstack-protector-all. Он доступен в GCC, начиная с его версии 4.9.

Все пакеты Fedora скомпилированы с помощью -fstack-protector, начиная с Fedora Core 5, и -fstack- protector-strongначиная с Fedora 20. Большинство пакетов в Ubuntu скомпилированы с помощью -fstack-protectorначиная с 6.10. Каждый пакет Arch Linux скомпилирован с -fstack-protectorс 2011 года. Все пакеты Arch Linux, созданные с 4 мая 2014 года, используют -fstack-protector-strong. Защита стека используется только для некоторых пакетов в Debian и только для базовой системы FreeBSD начиная с 8.0. Защита стека является стандартной в некоторых операционных системах, включая OpenBSD, Hardened Gentoo и DragonFly BSD.

StackGuard и ProPolice не могут защитить от переполнения в автоматически назначенных структурах, которые переполняются функцией указатели. ProPolice, по крайней мере, изменит порядок размещения, чтобы выделить такие структуры перед указателями функций. Отдельный механизм для защиты указателя был предложен в PointGuard и доступен в Microsoft Windows.

Microsoft Visual Studio

Пакет компиляторов от Microsoft реализует защиту от переполнения буфера с версии 2003 с помощью переключателя командной строки / GS, который по умолчанию включен с версии 2005. Использование / GS- отключает защиту.

IBM Compiler

Защита от разрушения стека может быть включена с помощью флага компилятора -qstackprotect.

Clang/LLVM

Clang поддерживает три буфера детекторы переполнения, а именно AddressSanitizer (-fsanitize = адрес), -fsanitize = bounds и SafeCode. Эти системы имеют разные компромиссы с точки зрения потери производительности, накладных расходов на память и классов обнаруженных ошибок. Защита стека является стандартной в некоторых операционных системах, включая OpenBSD.

Компилятор Intel

Компилятор Intel C и C ++ поддерживает защиту от разрушения стека с опциями, аналогичными тем, которые предоставляются GCC и Microsoft Visual Studio.

Fail-Safe C

Fail-Safe C - это безопасный для памяти компилятор ANSI C с открытым исходным кодом, который выполняет проверку границ на основе жирных указателей и объектно-ориентированного доступа к памяти.

StackGhost (аппаратный)

Изобретенный, StackGhost - это простая настройка процедур заполнения / заполнения окна регистров, которая значительно затрудняет использование переполнения буфера. Он использует уникальную аппаратную функцию архитектуры Sun Microsystems SPARC (а именно: отложенное заполнение / заполнение окна регистров в стеке) для обнаружения изменений возвращаемых указателей . (распространенный способ для эксплойта перехватить пути выполнения) прозрачно, автоматически защищая все приложения, не требуя изменений двоичного кода или исходного кода. Влияние на производительность незначительно, менее одного процента. Возникающие в результате проблемы с gdb были решены двумя годами позже, что позволило включить эту функцию. После этого события код StackGhost был интегрирован (и оптимизирован) в OpenBSD / SPARC.

Канарский пример

Нормальное выделение буфера для архитектур x86 и других подобных архитектур показано в записи переполнение буфера. Здесь мы покажем измененный процесс в отношении StackGuard.

При вызове функции создается кадр стека. Фрейм стека строится от конца памяти до начала; и каждый кадр стека помещается на вершину стека, ближайшую к началу памяти. Таким образом, переход от конца фрагмента данных в стековом фрейме изменяет данные, ранее введенные в стековый фрейм; и запуск с конца кадра стека помещает данные в предыдущий кадр стека. Типичный кадр стека может выглядеть, как показано ниже, с первым помещенным адресом возврата (RETA), за которым следует другая управляющая информация (CTLI).

(CTLI) (RETA)

В C функция может содержать множество различных структур данных для каждого вызова. Каждый фрагмент данных, созданный по запросу, помещается в кадр стека по порядку и, таким образом, упорядочивается от конца до начала памяти. Ниже представлена ​​гипотетическая функция и ее стековый фрейм.

int foo () {int a; / * целое число * / int * b; / * указатель на целое число * / char c [10]; / * символьные массивы * / char d [3]; б = а; / * инициализируем b, чтобы указать местоположение a * / strcpy (c, get_c ()); / * получаем откуда-то c, записываем в c * / * b = 5; / * данные в точке памяти, указываемой b, установлены на 5 * / strcpy (d, get_d ()); return * b; / * читать из b и передавать его вызывающему * /}
(d..) (c.........) (b...) (a...) (CTLI) ( RETA)

В этой гипотетической ситуации, если более десяти байтов записано в массив cили более 13 байтов в массив символов d, избыток будет переполнение в целочисленный указатель b, затем в целое число a, затем в управляющую информацию и, наконец, в адрес возврата. При перезаписи bуказатель заставляется ссылаться на любую позицию в памяти, вызывая чтение с произвольного адреса. Путем перезаписи RETA функцию можно заставить выполнять другой код (при попытке возврата), либо существующие функции (ret2libc ), либо код, записанный в стек во время переполнения.

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

Защита от переполнения буфера реализована как изменение компилятора. Таким образом, защита может изменять структуру данных в кадре стека. Именно так обстоит дело в таких системах, как ProPolice. Автоматические переменные вышеупомянутой функции переупорядочиваются более безопасно: массивы cи dвыделяются первыми в кадре стека, который помещает целое число aи целочисленный указатель бперед ними в памяти. Таким образом, кадр стека становится

(b...) (a...) (d..) (c.........) (CTLI) (RETA)

Поскольку невозможно переместить CTLI или RETA, не нарушив созданный код, используется другая тактика. Дополнительная информация, называемая «канарейкой» (CNRY), помещается после буферов в стековом фрейме. При переполнении буферов канареечное значение меняется. Таким образом, чтобы эффективно атаковать программу, злоумышленник должен оставить определенные признаки своей атаки. Кадр стека:

(b...) (a...) (d..) (c.........) (CNRY) (CTLI) (RETA)

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

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

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

См. Также

  • значок Портал компьютерного программирования

Ссылки

Внешние ссылки

Последняя правка сделана 2021-05-13 04:19:43
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте