Защита от переполнения буфера является любой из различных методов , используемых в процессе разработки программного обеспечения для повышения безопасности исполняемых программ пути обнаружения переполнения буфера на стек -allocated переменных, и предотвращая их от порождения программы поведения или стать серьезной безопасностьуязвимости. Переполнение буфера стека происходит, когда программа выполняет запись в адрес памяти в стеке вызовов программы за пределами предполагаемой структуры данных, которая обычно является буфером фиксированной длины. Ошибки переполнения буфера стека возникают, когда программа записывает в буфер, расположенный в стеке, больше данных, чем фактически выделено для этого буфера. Это почти всегда приводит к повреждению смежных данных в стеке, что может привести к сбоям программы, неправильной работе или проблемам с безопасностью.
Как правило, защита от переполнения буфера изменяет организацию данных, выделенных стеком, так что она включает канареечное значение, которое при уничтожении переполнением буфера стека показывает, что предшествующий ему буфер в памяти был переполнен. Путем проверки канареечного значения выполнение затронутой программы может быть прекращено, что предотвратит ее неправильное поведение или позволит злоумышленнику получить контроль над ней. Другие методы защиты от переполнения буфера включают проверку границ , которая проверяет доступ к каждому выделенному блоку памяти, чтобы они не могли выходить за пределы фактически выделенного пространства, и тегирование , которое гарантирует, что память, выделенная для хранения данных, не может содержать исполняемый код.
Переполнение буфера, выделенного в стеке, с большей вероятностью повлияет на выполнение программы, чем переполнение буфера в куче, потому что стек содержит адреса возврата для всех активных вызовов функций. Однако аналогичные средства защиты, зависящие от реализации, также существуют от переполнения на основе кучи.
Существует несколько реализаций защиты от переполнения буфера, в том числе для GNU Compiler Collection , LLVM , Microsoft Visual Studio и других компиляторов.
Обзор
Переполнение буфера стека происходит, когда программа выполняет запись в адрес памяти в стеке вызовов программы за пределами предполагаемой структуры данных, которая обычно является буфером фиксированной длины. Ошибки переполнения буфера стека возникают, когда программа записывает в буфер, расположенный в стеке, больше данных, чем фактически выделено для этого буфера. Это почти всегда приводит к повреждению смежных данных в стеке, а в случаях, когда переполнение было инициировано по ошибке, часто приводит к сбою или неправильной работе программы. Переполнение буфера стека - это тип более общей неисправности программирования, известной как переполнение буфера (или переполнение буфера). Переполнение буфера в стеке с большей вероятностью нарушит выполнение программы, чем переполнение буфера в куче, потому что стек содержит адреса возврата для всех активных вызовов функций. [1]
Переполнение буфера стека может быть вызвано намеренно как часть атаки, известной как разбиение стека . Если уязвимая программа работает с особыми привилегиями или принимает данные от ненадежных сетевых узлов (например, общедоступного веб-сервера ), то ошибка является потенциальной уязвимостью безопасности, которая позволяет злоумышленнику внедрить исполняемый код в работающую программу и принять контроль процесса. Это один из самых старых и надежных способов получения злоумышленниками несанкционированного доступа к компьютеру. [2]
Как правило, защита от переполнения буфера изменяет организацию данных в кадре стека при вызове функции, чтобы включить «канареечное» значение, которое при уничтожении показывает, что предшествующий ему буфер в памяти был переполнен. Это дает преимущество предотвращения целого класса атак. По мнению некоторых исследователей, [3] влияние этих методов на производительность незначительно.
Защита от разрушения стека не может защитить от определенных форм атак. Например, он не может защитить от переполнения буфера в куче. Не существует разумного способа изменить расположение данных в структуре ; Ожидается, что структуры модулей будут одинаковыми, особенно с разделяемыми библиотеками. Любые данные в структуре после буфера невозможно защитить с помощью канареек; таким образом, программисты должны быть очень осторожны с тем, как они организуют свои переменные и используют свои структуры.
Канареек
Канарейки или канареечные слова - это известные значения, которые помещаются между буфером и управляющими данными в стеке для отслеживания переполнения буфера. При переполнении буфера первыми поврежденными данными обычно будут данные канарейки, поэтому неудачная проверка данных канарейки будет предупреждать о переполнении, которое затем может быть обработано, например, путем аннулирования поврежденных данных. Не следует путать канареечное значение с контрольным значением .
Терминология является отсылкой к исторической практике использования канареек в угольных шахтах , поскольку они будут подвергаться воздействию токсичных газов раньше, чем шахтеры, что обеспечивает систему биологического предупреждения. Канарейки также известны как файлы cookie , которые предназначены для создания образа «сломанного файла cookie», когда значение повреждено.
Используются три типа канареек: терминатор , случайный и случайный XOR . Текущие версии StackGuard поддерживают все три, а ProPolice поддерживает терминатор и случайные канарейки.
Терминатор канарейки
Канарейки-терминаторы используют наблюдение, что большинство атак на переполнение буфера основаны на определенных строковых операциях, которые заканчиваются символами конца строки. Реакция на это наблюдение состоит в том, что канарейки состоят из нулевых терминаторов CR , LF и FF . В результате злоумышленник должен написать нулевой символ перед записью адреса возврата, чтобы избежать изменения канарейки. Это предотвращает атаки с использованием strcpy()
и других методов, которые возвращаются при копировании нулевого символа, в то время как нежелательный результат - известность канарейки. Даже с такой защитой злоумышленник потенциально может перезаписать канарейку с ее известным значением и контрольную информацию с несовпадающими значениями, передав таким образом код проверки канарейки, который выполняется незадолго до команды возврата из вызова конкретного процессора.
Случайные канарейки
Случайные канарейки генерируются случайным образом, обычно от демона сбора энтропии , чтобы злоумышленник не узнал их значение. Обычно читать канарейку на предмет эксплуатации невозможно с логической точки зрения или правдоподобно; канарейка - это безопасная ценность, известная только тем, кому она нужна - в данном случае это код защиты от переполнения буфера.
Обычно случайная канарейка генерируется при инициализации программы и сохраняется в глобальной переменной. Эта переменная обычно дополняется неотображенными страницами, поэтому попытка прочитать ее с помощью любых уловок, использующих ошибки для чтения из ОЗУ, вызывает ошибку сегментации, завершающую программу. Прочитать канарейку все еще можно, если злоумышленник знает, где она находится, или может заставить программу читать из стека.
Случайные канарейки XOR
Случайные канарейки XOR - это случайные канарейки, которые скремблируются с помощью XOR с использованием всех или части управляющих данных. Таким образом, после того, как канарейка или контрольные данные будут уничтожены, значение канарейки будет неправильным.
Случайные канарейки XOR имеют те же уязвимости, что и случайные канарейки, за исключением того, что метод «чтения из стека» получения канарейки немного сложнее. Злоумышленник должен получить канарейку, алгоритм и управляющие данные, чтобы заново сгенерировать исходную канарейку, необходимую для подделки защиты.
Кроме того, случайные канарейки XOR могут защитить от определенного типа атак, включающих переполнение буфера в структуре указателем, чтобы изменить указатель на часть управляющих данных. Из-за кодирования XOR канарейка будет ошибаться, если изменятся управляющие данные или возвращаемое значение. Из-за указателя управляющие данные или возвращаемое значение могут быть изменены без переполнения канарейки.
Хотя эти канарейки защищают управляющие данные от изменения с помощью затертых указателей, они не защищают никакие другие данные или сами указатели. Особой проблемой здесь являются указатели на функции, поскольку они могут быть переполнены и могут выполнять шелл-код при вызове.
Проверка границ
Проверка границ - это метод на основе компилятора, который добавляет информацию о границах времени выполнения для каждого выделенного блока памяти и проверяет все указатели на соответствие указателям во время выполнения. Для C и C ++ проверка границ может выполняться во время вычисления указателя [4] или во время разыменования. [5] [6] [7]
Реализации этого подхода использования либо в центральном хранилище, которое описывает каждый выделенный блок памяти, [4] [5] [6] или жира указатели , [7] , которые содержат как указатель и дополнительные данные, описывающие область , что они указывают на .
Добавление тегов
Тегирование [8] - это основанный на компиляторе или аппаратный (требующий тегированной архитектуры ) метод тегирования типа фрагмента данных в памяти, используемый в основном для проверки типов. Помечая определенные области памяти как неисполняемые, он эффективно предотвращает хранение исполняемого кода в памяти, выделенной для хранения данных. Кроме того, некоторые области памяти могут быть помечены как невыделенные, что предотвращает переполнение буфера.
Исторически тегирование использовалось для реализации языков программирования высокого уровня; [9] при соответствующей поддержке операционной системы тегирование также может использоваться для обнаружения переполнения буфера. [10] Примером является аппаратная функция NX bit , поддерживаемая процессорами Intel , AMD и ARM .
Реализации
Коллекция компиляторов GNU (GCC)
Защита от разрушения стека была впервые реализована StackGuard в 1997 году и опубликована на симпозиуме по безопасности USENIX в 1998 году . [11] StackGuard был представлен как набор исправлений для Intel x86 backend GCC 2.7. StackGuard поддерживался для дистрибутива Immunix Linux с 1998 по 2003 год и был расширен реализациями для терминатора, случайных и случайных канареек XOR. StackGuard был предложен для включения в GCC 3.x на конференции GCC 2003 Summit Proceedings [12], но этого так и не произошло.
С 2001 по 2005 год IBM разработала исправления GCC для защиты от разрушения стека, известные как ProPolice . [13] Он улучшил идею StackGuard, разместив буферы после локальных указателей и аргументов функций в кадре стека. Это помогло избежать повреждения указателей, предотвращая доступ к произвольным участкам памяти.
Однако инженеры Red Hat выявили проблемы с ProPolice и в 2005 году повторно реализовали защиту от разбиения стека для включения в GCC 4.1. [14] [15] В этой работе был представлен -fstack-protectorфлаг, который защищает только некоторые уязвимые функции, и -fstack-protector-allфлаг, который защищает все функции, независимо от того, нужны они им или нет. [16]
В 2012 году инженеры Google внедрили -fstack-protector-strongфлаг, чтобы добиться лучшего баланса между безопасностью и производительностью. [17] Этот флаг защищает больше видов уязвимых функций, чем защищает -fstack-protector, но не каждую функцию, обеспечивая лучшую производительность, чем -fstack-protector-all. Он доступен в GCC, начиная с его версии 4.9. [18]
Все пакеты Fedora компилируются с -fstack-protectorFedora Core 5 и -fstack-protector-strongс Fedora 20. [19] [20] Большинство пакетов в Ubuntu компилируются с -fstack-protector6.10. [21] Каждый пакет Arch Linux компилируется с -fstack-protector2011 года. [22] Все пакеты Arch Linux, созданные с 4 мая 2014 года, используют -fstack-protector-strong. [23] Защита стека используется только для некоторых пакетов в Debian , [24] и только для базовой системы FreeBSD, начиная с версии 8.0. [25] Защита стека является стандартной в некоторых операционных системах, включая OpenBSD , [26] Hardened Gentoo [27] и DragonFly BSD [ необходима ссылка ] .
StackGuard и ProPolice не могут защитить от переполнения в автоматически выделенных структурах, которые переходят в указатели функций. ProPolice, по крайней мере, изменит порядок размещения, чтобы выделить такие структуры перед указателями на функции. Отдельный механизм защиты указателя был предложен в PointGuard [28] и доступен в Microsoft Windows. [29]
Microsoft Visual Studio
Пакет компиляторов от Microsoft реализует защиту от переполнения буфера, начиная с версии 2003, с помощью переключателя командной строки / GS , который включен по умолчанию с версии 2005. [30] Использование / GS - отключает защиту.
Компилятор IBM
Защиту от разрушения стека можно включить с помощью флага компилятора -qstackprotect
. [31]
Clang / LLVM
Clang поддерживает три детектора переполнения буфера, а именно AddressSanitizer (-fsanitize = адрес), [6] -fsanitize = bounds, [32] и SafeCode. [33] Эти системы имеют разные компромиссы с точки зрения снижения производительности, накладных расходов на память и классов обнаруженных ошибок. Защита стека является стандартной в некоторых операционных системах, включая OpenBSD . [34]
Компилятор Intel
Компилятор Intel C и C ++ поддерживает защиту от разрушения стека с параметрами, аналогичными тем, которые предоставляются GCC и Microsoft Visual Studio. [35]
Отказоустойчивый C
Fail-Safe C [7] - это безопасный для памяти ANSI C-компилятор с открытым исходным кодом, который выполняет проверку границ на основе жирных указателей и объектно-ориентированного доступа к памяти. [36]
StackGhost (аппаратный)
Изобретенный Майк Frantzen , 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
или в массив символов d
, избыток будет переполняться в целочисленный указатель b
, затем в целое число a
, затем в управляющую информацию и, наконец, в адрес возврата. При перезаписи b
указатель заставляется ссылаться на любую позицию в памяти, вызывая чтение с произвольного адреса. Путем перезаписи RETA функцию можно заставить выполнять другой код (при попытке возврата), либо существующие функции ( ret2libc ), либо код, записанный в стек во время переполнения.
В двух словах, плохое обращение с c
и d
, например, неограниченная зЬгсру () вызывает выше, может позволить злоумышленнику контролировать программу путем воздействия на значения , присвоенные c
и d
непосредственно. Целью защиты от переполнения буфера является обнаружение этой проблемы наименее навязчивым способом. Это делается путем удаления того, что может быть небезопасным, и размещения своего рода натяжной проволоки или канарейки после буфера.
Защита от переполнения буфера реализована как изменение компилятора. Таким образом, защита может изменять структуру данных в кадре стека. Именно так обстоит дело в таких системах, как ProPolice . Автоматические переменные вышеупомянутой функции переупорядочиваются более безопасно: массивы c
и d
выделяются первыми в кадре стека, который помещает целочисленный a
и целочисленный указатели b
перед ними в памяти. Таким образом, кадр стека становится
(b ...) (a ...) (d ..) (c .........) (CTLI) (RETA)
Поскольку невозможно переместить CTLI или RETA, не нарушив созданный код, используется другая тактика. Дополнительная информация, называемая «канарейкой» (CNRY), помещается после буферов в стековом фрейме. Когда буферы переполняются, канареечное значение меняется. Таким образом, чтобы эффективно атаковать программу, злоумышленник должен оставить определенное указание на свою атаку. Кадр стека
(b ...) (a ...) (d ..) (c .........) (CNRY) (CTLI) (RETA)
В конце каждой функции есть инструкция, которая продолжает выполнение с адреса памяти, указанного RETA . Перед выполнением этой инструкции проверка CNRY гарантирует, что она не была изменена. Если значение CNRY не проходит тест, выполнение программы немедленно прекращается. По сути, как преднамеренные атаки, так и непреднамеренные ошибки программирования приводят к прерыванию программы.
Канарский метод добавляет несколько служебных инструкций для каждого вызова функции с автоматическим массивом, непосредственно перед всем распределением динамического буфера и после освобождения динамического буфера. Накладные расходы, возникающие при использовании этого метода, незначительны. Однако это работает, если только канарейка не останется неизменной. Если злоумышленник знает, что он там, и может определить ценность канарейки, он может просто скопировать ее с собой. Обычно это сложно сделать намеренно, и крайне маловероятно в непреднамеренных ситуациях.
Положение канарейки зависит от реализации, но всегда между буферами и защищенными данными. Различное положение и длина имеют разные преимущества.
Смотрите также
- Сторожевое значение (не путать с канареечным значением)
- Целостность потока управления
- Рандомизация разметки адресного пространства
- Исполняемая защита пространства
- Отладчик памяти
- PaX
- Статический анализ кода
Рекомендации
- ^ Фитен, Уильям Л .; Сикорд, Роберт (27 марта 2007 г.). «ВТ-МБ. Нарушение границ памяти» . США CERT .
- ^ Леви, Элиас (1996-11-08). «Разбить стек ради удовольствия и прибыли» . Phrack . 7 (49): 14.
- ^ «Переполнение буфера: атаки и защита от уязвимости десятилетия *» (PDF) . Архивировано из оригинального (PDF) 09 марта 2013 года.
- ^ а б «Проверка границ для C» . Doc.ic.ac.uk. Архивировано из оригинала на 2016-03-26 . Проверено 27 апреля 2014 .
- ^ а б «SAFECode: безопасная виртуальная архитектура» . Sva.cs.illinois.edu. 2009-08-12 . Проверено 27 апреля 2014 .
- ^ а б в "Google / дезинфицирующие средства" .
- ^ а б в «Отказоустойчивый C: первая страница» . Staff.aist.go.jp. 2013-05-07. Архивировано из оригинала на 2016-07-07 . Проверено 27 апреля 2014 .
- ^ «Вторник, 5 апреля 2005 г.» (PDF) . Feustel.us . Архивировано из оригинального (PDF) 23 июня 2016 года . Проверено 17 сентября 2016 .
- ^ «Теги и проверка типов в LISP: аппаратный и программный подходы» . ACM.
- ^ «Обзор безопасности MCP серверов ClearPath Enterprise Servers» (PDF) . Public.support.unisys.com. Архивировано из оригинального (PDF) 24 января 2013 года . Проверено 27 апреля 2014 .
- ^ «Доклады - 7-й симпозиум по безопасности USENIX, 1998 г.» . Usenix.org. 2002-04-12 . Проверено 27 апреля 2014 .
- ^ «Материалы саммита разработчиков GCC» (PDF) . Май 2003. Архивировано 15 июля 2004 года . Проверено 17 сентября 2016 .CS1 maint: bot: исходный статус URL неизвестен ( ссылка )
- ^ «Расширение GCC для защиты приложений от атак, разбивающих стек» . Research.ibm.com . Проверено 27 апреля 2014 .
- ^ «Серия выпусков GCC 4.1 - Изменения, новые функции и исправления - Проект GNU - Фонд свободного программного обеспечения (FSF)» . Gcc.gnu.org . Проверено 27 апреля 2014 .
- ^ «Ричард Хендерсон - [rfc] повторная реализация средства защиты стека от IBM» . Gcc.gnu.org . Проверено 27 апреля 2014 .
- ^ «Параметры оптимизации - Использование коллекции компиляторов GNU (GCC)» . Gcc.gnu.org . Проверено 27 апреля 2014 .
- ^ «Хан Шэнь (ææ) - [ПАТЧ] Добавить новую опцию« -fstack-protector-strong »(патч / документ внутри)» . Gcc.gnu.org. 2012-06-14 . Проверено 27 апреля 2014 .
- ^ Эдж, Джейк (5 февраля 2014 г.). « » Защита Strong «стек для GCC» . Еженедельные новости Linux . Проверено 28 ноября 2014 .
Он попал в GCC 4.9.
- ^ «Функции безопасности» . FedoraProject. 2013-12-11 . Проверено 27 апреля 2014 .
- ^ "# 1128 (переключение с" -fstack-protector "на" -fstack-protector-strong "в Fedora 20) - FESCo" . Fedorahosted.org . Проверено 27 апреля 2014 .
- ^ «Безопасность / Возможности - Ubuntu Wiki» . Wiki.ubuntu.com . Проверено 27 апреля 2014 .
- ^ «FS # 18864: рассмотрите возможность включения защиты GCC от разрушения стека (ProPolice, SSP) для всех пакетов» . Bugs.archlinux.org . Проверено 27 апреля 2014 .
- ^ "svntogit / packages.git - Git клон репозитория пакетов" .
- ^ «Статистика повышения безопасности Debian» . Outflux.net . Проверено 27 апреля 2014 .
- ^ «Примечания к выпуску FreeBSD 8.0-RELEASE» . Freebsd.org. 2013-11-13 . Проверено 27 апреля 2014 .
- ^ "Страница руководства OpenBSD gcc-local (1)" .
gcc поставляется с расширением защиты стека ProPolice , которое по умолчанию включено.
- ^ «Усиленный / Toolchain - Gentoo Wiki» . 2016-07-31.
GCC с усиленной защитой Gentoo по умолчанию включает протектор стека, если явно не запрошено не делать этого.
- ^ «12-й симпозиум по безопасности USENIX - Технический доклад» .
- ^ «Блоги MSDN - получайте самую свежую информацию, идеи, объявления и новости от экспертов и разработчиков Microsoft в блогах MSDN» .
- ^ "/ GS (Проверка безопасности буфера) (C ++)" . msdn.microsoft.com . Проверено 27 апреля 2014 .
- ^ "qstackprotect" . Publib.boulder.ibm.com . Проверено 27 апреля 2014 .
- ^ «Руководство пользователя компилятора Clang - документация по Clang 3.5» . Clang.llvm.org . Проверено 27 апреля 2014 .
- ^ «SAFECode» . Safecode.cs.illinois.edu . Проверено 27 апреля 2014 .
- ^ "Страница руководства OpenBSD clang-local (1)" .
clang поставляется с включенной по умолчанию защитой стека, что эквивалентно параметру -fstack-protector-strong в других системах.
- ^ «Справочное руководство для Intel C ++ Compiler 15.0: fstack-security-check, GS» . software.intel.com . Проверено 13 февраля 2015 .
- ^ "thesis.dvi" (PDF) . Staff.aist.go.jp . Проверено 17 сентября 2016 .
Внешние ссылки
- Протоколы саммита GCC 2003 (PDF)
- Разбивая стопку ради удовольствия и выгоды от Aleph One
- Официальный дом ProPolice
- Домашняя страница Immunix StackGuard
- Исходная бумага StackGuard в USENIX Security 1998
- StackGhost: аппаратная защита стека
- Реализация прополиса в FreeBSD 5.4 и 6.2
- Четыре разных приема обойти защиту StackShield и StackGuard
- Защитник от разрушения стека