В информационной безопасности и программирование, переполнение буфера или переполнение буфера, является аномалией, где программа при записи данных в буфер , выходит за границы буфера и перезаписывает соседние ячейки памяти.
Буферы - это области памяти, отведенные для хранения данных, часто при перемещении их из одного раздела программы в другой или между программами. Переполнение буфера часто может быть вызвано неверно сформированными входными данными; если предположить, что все входные данные будут меньше определенного размера, и буфер создан с таким размером, то аномальная транзакция, которая производит больше данных, может привести к тому, что запись будет за пределами конца буфера. Если при этом перезаписываются соседние данные или исполняемый код, это может привести к ошибочному поведению программы, включая ошибки доступа к памяти, неверные результаты и сбои.
Использование поведения переполнения буфера является хорошо известным уязвимостью безопасности.. Во многих системах структура памяти программы или системы в целом четко определена. Отправляя данные, предназначенные для того, чтобы вызвать переполнение буфера, можно производить запись в области, которые, как известно, содержат исполняемый код, и заменять его вредоносным кодом или выборочно перезаписывать данные, относящиеся к состояние программы, что вызывает поведение, не предназначенное исходным программистом. Буферы широко распространены в коде операционной системы (ОС), поэтому можно совершать атаки, которые выполняют повышение привилегий и получают неограниченный доступ к ресурсам компьютера. Знаменитый червь Морриса в 1988 году использовал это как одну из техник атаки.
Языки программирования, обычно связанные с переполнением буфера, включают C и C ++, которые не обеспечивают встроенной защиты от доступа или перезаписи данных в любой части памяти и не автоматически проверять, что данные, записанные в массив (тип встроенного буфера), находятся в пределах этого массива. Проверка границ может предотвратить переполнение буфера, но требует дополнительного кода и времени обработки. Современные операционные системы используют различные методы борьбы со злонамеренным переполнением буфера, в частности, путем рандомизации структуры памяти или преднамеренного выделения пространства между буферами и поиска действий, которые записываются в эти области («канарейки»).
Переполнение буфера происходит, когда данные, записанные в буфер, также искажают значения данных в адресах памяти, смежных с буфер назначения из-за недостаточной проверки границ . Это может произойти при копировании данных из одного буфера в другой без предварительной проверки их соответствия целевому буферу.
В следующем примере, выраженном на C, программа имеет две соседние переменные в памяти: строковый буфер длиной 8 байт, A и двухбайтовое целое число с прямым порядком байтов, B.
char A [8] = ""; беззнаковый короткий B = 1979;
Изначально A не содержит ничего, кроме нулевых байтов, а B содержит номер 1979.
имя переменной | A | B | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
значение | [нулевая строка ] | 1979 | ||||||||
шестнадцатеричное значение | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 07 | BB |
Теперь программа пытается для хранения строки с завершающим нулем «лишнее»
с кодировкой ASCII в буфере A.
strcpy (A, "чрезмерно");
"избыточный"
имеет длину 9 символов и кодируется до 10 байтов, включая нулевой терминатор, но A может занимать только 8 байтов. Не проверяя длину строки, он также перезаписывает значение B:
имя переменной | A | B | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
значение | 'e' | 'x' | 'c' | 'e ' | ' s ' | ' s ' | ' i ' | ' v ' | 25856 | |
hex | 65 | 78 | 63 | 65 | 73 | 73 | 69 | 76 | 65 | 00 |
Значение B теперь непреднамеренно заменено числом, образованным из части строка символов. В этом примере "e", за которым следует нулевой байт, будет равно 25856.
Операционная система может иногда обнаруживать запись данных после конца выделенной памяти и генерировать ошибку segmentation fault, которая приводит к завершает процесс.
Чтобы предотвратить переполнение буфера в этом примере, вызов strcpy можно заменить на strlcpy , который принимает максимальную емкость A (включая символ завершения нуля) в качестве дополнительного параметра и гарантирует, что не более этого объема данных будет записано в A:
strlcpy (A, "чрезмерно", sizeof (A));
Если доступно, библиотечная функция strlcpy предпочтительнее, чем strncpy , которая не завершает буфер назначения нулем, если источник длина строки больше или равна размеру буфера (третий аргумент, переданный функции), поэтому Aне может оканчиваться нулем и не может рассматриваться как допустимая строка в стиле C.
Способы эксплуатации уязвимости, связанной с переполнением буфера, различаются в зависимости от архитектуры, операционной системы и области памяти. Например, эксплуатация в куче (используется для динамически выделяемой памяти) заметно отличается от эксплуатации в стеке вызовов .
Технически подкованный пользователь может использовать переполнение буфера на основе стека, чтобы манипулировать программой в своих интересах одним из нескольких способов:
Злоумышленник создает данные, чтобы вызвать один из этих эксплойтов, а затем помещает эти данные в буфер, предоставленный для пользователей уязвимым кодом. Если адрес предоставленных пользователем данных, используемых для воздействия на переполнение буфера стека, непредсказуем, использование переполнения буфера стека для вызова удаленного выполнения кода становится намного сложнее. Один из методов, который можно использовать для использования такого переполнения буфера, называется «захват ». В этом методе злоумышленник найдет указатель на уязвимый буфер стека и вычислит положение своего шелл-кода относительно этого указателя. Затем они будут использовать перезапись для перехода к инструкции , уже находящейся в памяти, которая совершит второй переход, на этот раз относительно указателя; этот второй переход приведет к переходу выполнения в шеллкод. Подходящие инструкции часто присутствуют в большом коде. Metasploit Project, например, поддерживает базу данных подходящих кодов операций, хотя в нем перечислены только те, которые находятся в операционной системе Windows.
Переполнение буфера, происходящее в области данных кучи, называется переполнением кучи и может использоваться способом, отличным от переполнения на основе стека. Память в куче динамически выделяется приложением во время выполнения и обычно содержит данные программы. Эксплуатация осуществляется путем повреждения этих данных определенными способами, чтобы приложение перезаписало внутренние структуры, такие как указатели связанных списков. Техника канонического переполнения кучи перезаписывает привязку распределения динамической памяти (например, malloc метаданные) и использует полученный обмен указателем для перезаписи указателя программной функции.
Уязвимость GDI + Microsoft при обработке JPEG является примером опасности, которую может представлять переполнение кучи.
Манипуляции с буфером, которые происходят до его чтения или выполнения, могут привести к сбою попытки эксплуатации. Эти манипуляции могут снизить угрозу эксплуатации, но не могут сделать ее невозможной. Манипуляции могут включать преобразование в верхний или нижний регистр, удаление метасимволов и фильтрацию не- буквенно-цифровых строк. Однако существуют методы, позволяющие обойти эти фильтры и манипуляции; буквенно-цифровой код, полиморфный код, самомодифицирующийся код и атаки return-to-libc. Те же методы могут использоваться для предотвращения обнаружения системами обнаружения вторжений. В некоторых случаях, в том числе при преобразовании кода в Unicode, угроза уязвимости была неверно представлена раскрывающими как только отказ в обслуживании, когда фактически возможно удаленное выполнение произвольного кода.
В реальных эксплойтах существует множество проблем, которые необходимо преодолеть, чтобы эксплойты работали надежно. Эти факторы включают в себя нулевые байты в адресах, вариативность расположения шелл-кода, различия между средами и различные контрмеры в работе.
NOP-салазки - самый старый и наиболее широко известный метод использования переполнения буфера стека. Он решает проблему нахождения точного адреса буфера за счет эффективного увеличения размера целевой области. Для этого большие участки стека повреждаются машинной командой no-op. В конце предоставленных злоумышленником данных, после инструкций по бездействию, злоумышленник помещает инструкцию для выполнения относительного перехода в верхнюю часть буфера, где находится шелл-код . Этот набор запретов операций называется «салазками без операций», потому что, если адрес возврата перезаписывается любым адресом в пределах области буфера без операций, выполнение будет «скользить» вниз по запретным операциям до тех пор, пока оно не будет перенаправляется к собственно вредоносному коду скачком в конце. Этот метод требует, чтобы злоумышленник угадал, где в стеке находится NOP-салазки вместо сравнительно небольшого шелл-кода.
Из-за популярности этого метода многие поставщики систем предотвращения вторжений будут найдите этот шаблон бездействующих машинных инструкций в попытке обнаружить используемый шелл-код. Важно отметить, что NOP-салазки не обязательно содержат только традиционные безоперационные машинные инструкции; любая инструкция, которая не повреждает состояние машины до такой степени, что шеллкод не запускается, может использоваться вместо аппаратного отказа. В результате для разработчиков эксплойтов стало обычной практикой составлять безоперационные салазки со случайно выбранными инструкциями, которые не будут иметь реального влияния на выполнение шеллкода.
Хотя этот метод значительно увеличивает шансы того, что атака будет успешным, не без проблем. Эксплойты, использующие эту технику, по-прежнему должны полагаться на некоторую удачу, поскольку они угадывают смещения в стеке, которые находятся в области NOP-sled. Неверное предположение обычно приводит к сбою целевой программы и может предупредить системного администратора о действиях злоумышленника. Другая проблема заключается в том, что для салазок NOP требуется гораздо больший объем памяти для размещения салазок NOP, достаточно больших, чтобы их можно было использовать. Это может быть проблемой, когда выделенный размер затронутого буфера слишком мал, а текущая глубина стека мала (т. Е. От конца текущего кадра стека до начала стека не так много места). Несмотря на свои проблемы, NOP-салазки часто являются единственным методом, который работает для данной платформы, среды или ситуации, и как таковой он по-прежнему остается важным методом.
Техника «перехода к регистру» позволяет надежно использовать переполнение буфера стека без необходимости дополнительного места для NOP-салазок и без угадать смещения стека. Стратегия состоит в том, чтобы перезаписать указатель возврата чем-то, что заставит программу перейти к известному указателю, хранящемуся в регистре, который указывает на управляемый буфер и, следовательно, на шеллкод. Например, если регистр A содержит указатель на начало буфера, то любой переход или вызов, принимающий этот регистр в качестве операнда, можно использовать для получения контроля над потоком выполнения.
Инструкция из ntdll.dll для вызоваDbgPrint ()
процедура содержит машинный код операции i386 для jmp esp
.На практике программа может не содержать преднамеренно инструкции для перехода к конкретному регистру. Традиционное решение - найти случайный экземпляр подходящего кода операции в фиксированном месте где-нибудь в памяти программы. На рисунке E слева показан пример такого непреднамеренного экземпляра инструкции i386 jmp esp
. Код операции для этой инструкции: FF E4
. Эта двухбайтовая последовательность может быть найдена по однобайтовому смещению от начала инструкции вызвать DbgPrint
по адресу 0x7C941EED
. Если злоумышленник перезаписывает адрес возврата программы этим адресом, программа сначала перейдет на 0x7C941EED
, интерпретирует код операции FF E4
как инструкцию jmp esp
и выполнит затем перейдите на вершину стека и выполните код злоумышленника.
Когда этот метод возможен, серьезность уязвимости значительно возрастает. Это связано с тем, что эксплойт будет работать достаточно надежно, чтобы автоматизировать атаку с виртуальной гарантией успеха при ее запуске. По этой причине этот метод наиболее часто используется в Интернет-червях, использующих уязвимости переполнения буфера стека.
Этот метод также позволяет размещать шеллкод после перезаписанного адреса возврата на платформе Windows.. Поскольку исполняемые файлы в основном основаны на адресе 0x00400000
, а x86 - это архитектура Little Endian, последний байт адреса возврата должен быть нулевым, что завершает копирование буфера, и ничего не записывается за пределами который. Это ограничивает размер шелл-кода размером буфера, который может быть чрезмерно ограничительным. Библиотеки DLL расположены в верхней памяти (выше 0x01000000
) и поэтому имеют адреса, не содержащие нулевых байтов, поэтому этот метод может удалить нулевые байты (или другие запрещенные символы) из перезаписанного адреса возврата. Используемый таким образом метод часто называют «батутом DLL».
Для обнаружения или предотвращения переполнения буфера использовались различные методы с различными компромиссами. Самый надежный способ избежать или предотвратить переполнение буфера - использовать автоматическую защиту на уровне языка. Однако такого рода защиту нельзя применить к унаследованному коду, и часто технические, деловые или культурные ограничения требуют уязвимого языка. В следующих разделах описываются доступные варианты и реализации.
Ассемблер и C / C ++ - популярные языки программирования, которые уязвимы для переполнения буфера, отчасти потому, что они позволяют прямой доступ к памяти и не строго типизированы. C не обеспечивает встроенной защиты от доступа или перезаписи данных в любой части памяти; более конкретно, он не проверяет, находятся ли данные, записанные в буфер, в пределах этого буфера. Стандартные библиотеки C ++ предоставляют множество способов безопасной буферизации данных, а стандартная библиотека шаблонов Standard Template Library (STL) C ++ предоставляет контейнеры, которые могут дополнительно выполнять проверку границ, если программист явно вызывает проверки при доступе к данным. Например, функция-член vector
at ()
выполняет проверку границ и выдает исключение out_of_range
, если проверка границ завершается неудачно.. Однако C ++ ведет себя так же, как C, если проверка границ не вызывается явно. Методы предотвращения переполнения буфера существуют также для C.
Строго типизированные языки, не допускающие прямого доступа к памяти, такие как COBOL, Java, Python и другие, в большинстве случаев предотвращают переполнение буфера. Многие языки программирования, отличные от C / C ++, обеспечивают проверку во время выполнения, а в некоторых случаях даже проверку во время компиляции, которая может отправлять предупреждение или вызывать исключение, когда C или C ++ перезаписывают данные и продолжают выполнять дальнейшие инструкции до получения ошибочных результатов, которые могут или может не привести к сбою программы. Примеры таких языков включают Ada, Eiffel, Lisp, Modula-2, Smalltalk, OCaml и такие C-производные, как Cyclone, Rust и D. Среды байт-кода Java и .NET Framework также требуют проверки границ для всех массивов. Почти каждый интерпретируемый язык будет защищать от переполнения буфера, сигнализируя о четко определенном состоянии ошибки. Часто, когда язык предоставляет достаточно информации о типе для выполнения проверки границ, предоставляется опция для его включения или отключения. Статический анализ кода может удалить многие динамические проверки привязки и типов, но плохие реализации и неудобные случаи могут значительно снизить производительность. Инженеры-программисты должны тщательно учитывать компромисс между безопасностью и затратами на производительность при выборе языка и настроек компилятора.
Проблема переполнения буфера обычна для языков C и C ++, поскольку они раскрывают низкоуровневые детали представления буферов как контейнеров для типов данных. Таким образом, следует избегать переполнения буфера, поддерживая высокую степень правильности кода, который выполняет управление буфером. Также давно рекомендуется избегать стандартных библиотечных функций, для которых не проверяются границы, таких как gets
, scanf
и strcpy
. Червь Morris использовал , получает вызов
в fingerd.
. Хорошо написанные и протестированные библиотеки абстрактных типов данных, которые централизуют и автоматически выполняют управление буфером, включая проверку границ, могут уменьшить возникновение и влияние переполнения буфера. Двумя основными типами данных строительных блоков в этих языках, в которых часто случаются переполнения буфера, являются строки и массивы; таким образом, библиотеки, предотвращающие переполнение буфера в этих типах данных, могут обеспечить подавляющее большинство необходимого покрытия. Тем не менее, неправильное использование этих безопасных библиотек может привести к переполнению буфера и другим уязвимостям; и, естественно, любая ошибка в самой библиотеке является потенциальной уязвимостью. «Безопасные» реализации библиотеки включают «Лучшую библиотеку строк», Vstr и Erwin. Библиотека C операционной системы OpenBSD предоставляет функции strlcpy и strlcat, но они более ограничены, чем полностью безопасные реализации библиотеки.
В сентябре 2007 г. был опубликован Технический отчет 24731, подготовленный комитетом по стандартам C; он определяет набор функций, которые основаны на строковых функциях стандартной библиотеки C и функциях ввода-вывода с дополнительными параметрами размера буфера. Однако эффективность этих функций с целью уменьшения переполнения буфера спорна; это требует вмешательства программиста для каждого вызова функции, что эквивалентно вмешательству, которое может сделать аналогичные старые стандартные библиотечные функции безопасным от переполнения буфера.
Защита от переполнения буфера используется для обнаружения наиболее распространенное переполнение буфера происходит при проверке того, что стек не был изменен при возврате функции. Если он был изменен, программа завершается с ошибкой сегментации . Три таких системы - это Libsafe и патчи StackGuard и ProPolice gcc.
Реализация Microsoft режима Data Execution Prevention (DEP) явно защищает указатель на Structured Exception Handler (SEH) от перезаписи.
Более надежная защита стека возможна путем разделения стека на два: один для данных и один для возврата функции. Это разделение присутствует в языке Forth, хотя это не было дизайнерским решением, основанным на безопасности. Тем не менее, это не полное решение проблемы переполнения буфера, поскольку конфиденциальные данные, отличные от адреса возврата, могут быть перезаписаны.
Переполнение буфера работает путем манипулирования указателями, включая сохраненные адреса. PointGuard был предложен в качестве расширения компилятора, чтобы злоумышленники не могли надежно манипулировать указателями и адресами. Подход работает, когда компилятор добавляет код для автоматического XOR-кодирования указателей до и после их использования. Теоретически, поскольку злоумышленник не знает, какое значение будет использоваться для кодирования / декодирования указателя, он не может предсказать, на что он будет указывать, если он перезапишет его новым значением. PointGuard так и не был выпущен, но Microsoft реализовала аналогичный подход, начиная с Windows XP SP2 и Windows Server 2003 SP1. Вместо того, чтобы реализовать защиту указателя как автоматическую функцию, Microsoft добавила процедуру API, которую можно вызывать. Это позволяет повысить производительность (поскольку не используется все время), но возлагает на программиста бремя, чтобы знать, когда это необходимо.
Поскольку XOR является линейным, злоумышленник может иметь возможность манипулировать закодированным указателем, перезаписывая только младшие байты адреса. Это может позволить атаке быть успешной, если злоумышленник может попытаться использовать эксплойт несколько раз или может завершить атаку, заставив указатель указывать на одно из нескольких мест (например, любое место в цепочке NOP). Microsoft добавила случайную ротацию в свою схему кодирования, чтобы устранить эту уязвимость для частичной перезаписи.
Защита исполняемого пространства - это подход к защите от переполнения буфера, который предотвращает выполнение кода в стеке или куча. Злоумышленник может использовать переполнение буфера для вставки произвольного кода в память программы, но с защитой исполняемого пространства любая попытка выполнить этот код вызовет исключение.
Некоторые процессоры поддерживают функцию под названием NX («No eXecute») или XD («eXecute Disabled») бит, которые можно использовать вместе с программным обеспечением. пометить страницы данных (например, те, которые содержат стек и кучу) как доступные для чтения и записи, но не исполняемые.
Некоторые операционные системы Unix (например, OpenBSD, macOS ) поставляются с защитой исполняемого пространства (например, W ^ X ). Некоторые дополнительные пакеты включают:
Более новые варианты Microsoft Windows также поддерживают защиту исполняемого пространства, называемую Data Execution Prevention. Proprietary к надстройкам относятся:
Защита исполняемого пространства обычно не защищает от атак возврата к libc или любых других атак, которые не зависят от исполнение кода злоумышленника. Однако в 64-битных системах, использующих ASLR, как описано ниже, защита исполняемого пространства значительно затрудняет выполнение таких атак.
Рандомизация компоновки адресного пространства (ASLR) - это функция компьютерной безопасности, которая включает в себя упорядочивание позиций областей ключевых данных, обычно включая основу исполняемого файла и положение библиотек, куча и стек случайным образом в адресном пространстве процесса.
Рандомизация адресов виртуальной памяти, по которым могут быть найдены функции и переменные, может сделать использование переполнения буфера более трудным, но не невозможным. Он также вынуждает злоумышленника адаптировать попытку эксплуатации к конкретной системе, что препятствует попыткам интернет-червей. Аналогичный, но менее эффективный метод - перебазировать процессы и библиотеки в виртуальном адресном пространстве.
Использование глубокой проверки пакетов (DPI) позволяет обнаруживать на сетевом периметре очень простые удаленные попытки использовать переполнение буфера с помощью сигнатур атак и эвристики . Они могут блокировать пакеты, которые имеют сигнатуру известной атаки, или, если обнаруживается длинная серия инструкций No-Operation (известных как NOP-sled), они когда-то использовались, когда местоположение полезной нагрузки эксплойта немного варьируется.
Сканирование пакетов не является эффективным методом, поскольку оно может предотвратить только известные атаки, и существует множество способов кодирования NOP-салазок. Shellcode, используемый злоумышленниками, может быть буквенно-цифровым, метаморфическим или самомодифицирующимся, чтобы избежать обнаружения эвристическими сканерами пакетов и системы обнаружения вторжений.
Проверка на переполнение буфера и исправление ошибок, которые вызывают их, естественным образом помогает предотвратить переполнение буфера. Одним из распространенных автоматизированных методов их обнаружения является фаззинг. Пограничное тестирование также может выявить переполнение буфера, как и статический анализ. Как только обнаруживается потенциальное переполнение буфера, его необходимо исправить; это делает подход к тестированию полезным для программного обеспечения, которое находится в разработке, но менее полезным для устаревшего программного обеспечения, которое больше не поддерживается или не поддерживается.
Переполнение буфера было изучено и частично публично задокументировано еще в 1972 году, когда в Исследовании планирования технологий компьютерной безопасности был изложен следующий метод: «Код, выполняющий эту функцию, не проверяет источник и адрес назначения правильно, что позволяет пользователю перекрывать части монитора. Это может быть использовано для ввода кода в монитор, который позволит пользователю получить контроль над машиной ». Сегодня монитор будет называться ядром.
Самая ранняя задокументированная враждебная эксплуатация переполнения буфера была зафиксирована в 1988 году. Это был один из нескольких эксплойтов, использованных червем Морриса для распространения через Интернет. Используемая программа представляла собой службу в Unix под названием finger. Позже, в 1995 году, Томас Лопатич независимо заново обнаружил переполнение буфера и опубликовал свои выводы в списке рассылки по безопасности Bugtraq. Год спустя, в 1996 году, Элиас Леви (также известный как Aleph One) опубликовал в журнале Phrack статью «Smashing the Stack for Fun and Profit» - пошаговое введение в эксплуатацию уязвимостей переполнения буфера на основе стека.
С тех пор по крайней мере два основных интернет-червя использовали переполнение буфера для компрометации большого количества систем. В 2001 году червь Code Red использовал переполнение буфера в Microsoft Internet Information Services (IIS) 5.0, а в 2003 году червь SQL Slammer скомпрометировал компьютеры, на которых запущено Microsoft SQL Server 2000.
В 2003 году переполнение буфера, присутствующее в лицензированных играх для Xbox, использовалось для того, чтобы нелицензионное программное обеспечение, включая игры для домашнего приготовления, могло запускаться на консоли без необходимости для модификаций оборудования, известных как модчипы. PS2 Independence Exploit также использовал переполнение буфера, чтобы добиться того же для PlayStation 2. Хак «Сумерек» достиг того же с Wii, используя переполнение буфера в The Legend of Zelda: Twilight Princess.