Переполнение буфера - Buffer overflow

В информационной безопасности и программировании , с переполнением буфера , или буфер перерасхода , это аномалия , где программа , при записи данных в буфер , перерасход этого буфера границы и переписывает соседние памяти места.

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

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

Языки программирования, обычно связанные с переполнением буфера, включают C и C ++ , которые не обеспечивают встроенной защиты от доступа или перезаписи данных в любой части памяти и не проверяют автоматически, что данные, записанные в массив (тип встроенного буфера), находятся в пределах границы этого массива. Проверка границ может предотвратить переполнение буфера, но требует дополнительного кода и времени обработки. Современные операционные системы используют различные методы борьбы со злонамеренным переполнением буфера, в частности, путем рандомизации компоновки памяти или намеренного выделения пространства между буферами и поиска действий, которые записываются в эти области («канарейки»).

Техническое описание

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

Пример

В следующем примере, выраженном на языке C , программа имеет две соседние в памяти переменные: 8-байтовый строковый буфер A и двухбайтовое целое число с прямым порядком байтов B.

char           A[8] = "";
unsigned short B    = 1979;

Изначально A содержит только нулевые байты, а B содержит число 1979.

имя переменной А B
ценить [ пустая строка ] 1979 г.
шестнадцатеричное значение 00 00 00 00 00 00 00 00 07 BB

Теперь программа пытается сохранить строку "excessive" с завершающим нулем в кодировке ASCII в буфере A.

strcpy(A, "excessive");

"excessive"имеет длину 9 символов и кодируется до 10 байтов, включая нулевой терминатор, но A может занимать только 8 байтов. Не проверяя длину строки, он также перезаписывает значение B:

имя переменной А B
ценить 'e' 'x' 'c' 'e' 's' 's' 'i' 'v' 25856
шестнадцатеричный 65 78 63 65 73 73 69 76 65 00

Значение B теперь непреднамеренно заменено числом, образованным из части символьной строки. В этом примере "e", за которым следует нулевой байт, станет 25856.

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

Чтобы предотвратить переполнение буфера в этом примере, вызов strcpyможет быть заменен на strlcpy, который принимает максимальную емкость A (включая символ завершения нуля) в качестве дополнительного параметра и гарантирует, что будет записано не более этого количества данных. к A:

strlcpy(A, "excessive", sizeof(A));

Если она доступна, strlcpyпредпочтительнее использовать библиотечную функцию, strncpyкоторая не завершает буфер назначения нулем, если длина исходной строки больше или равна размеру буфера (третий аргумент, переданный функции), поэтому Aне может быть нулевым. завершается и не может рассматриваться как допустимая строка в стиле C.

Эксплуатация

Способы использования уязвимости переполнения буфера различаются в зависимости от архитектуры , операционной системы и области памяти. Например, эксплуатация в куче (используемой для динамически выделяемой памяти) заметно отличается от эксплуатации в стеке вызовов .

Эксплуатация на основе стека

Технически подкованный пользователь может использовать переполнение буфера на основе стека, чтобы манипулировать программой в своих интересах одним из нескольких способов:

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

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

Эксплуатация на основе кучи

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

Microsoft «s GDI + уязвимость в обработке JPEGs является примером опасности переполнения кучи может представить.

Барьеры к эксплуатации

Манипуляции с буфером, которые происходят до того, как он будет прочитан или выполнен, могут привести к сбою попытки эксплуатации. Эти манипуляции могут снизить угрозу эксплуатации, но не могут сделать ее невозможной. Манипуляции могут включать преобразование в верхний или нижний регистр, удаление метасимволов и фильтрацию не буквенно-цифровых строк. Однако существуют методы, позволяющие обойти эти фильтры и манипуляции; буквенно-цифровой код , полиморфный код , самомодифицирующийся код и атаки с возвратом в libc . Те же методы можно использовать, чтобы избежать обнаружения системами обнаружения вторжений . В некоторых случаях, в том числе при преобразовании кода в Unicode , угроза уязвимости была неверно представлена ​​раскрывающими как отказ в обслуживании, когда фактически возможно удаленное выполнение произвольного кода.

Практичность эксплуатации

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

Санная техника NOP

Иллюстрация полезной нагрузки NOP-салазок в стеке.

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

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

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

Переход к адресу, хранящемуся в регистровой технике

Техника «перехода к регистру» позволяет надежно использовать переполнение буфера стека без необходимости в дополнительном пространстве для NOP-салазок и без необходимости угадывать смещения стека. Стратегия состоит в том, чтобы перезаписать указатель возврата чем-то, что заставит программу перейти к известному указателю, хранящемуся в регистре, который указывает на управляемый буфер и, следовательно, на шеллкод. Например, если регистр A содержит указатель на начало буфера, то любой переход или вызов, принимающий этот регистр в качестве операнда, можно использовать для получения контроля над потоком выполнения.

Инструкция от ntdll.dll для вызова DbgPrint()подпрограммы содержит машинный код операции i386 для jmp esp.

На практике программа может не содержать преднамеренно инструкции для перехода к определенному регистру. Традиционное решение - найти непреднамеренный экземпляр подходящего кода операции в фиксированном месте где-то в памяти программы. На рисунке E слева показан пример такого непреднамеренного экземпляра jmp espинструкции i386 . Код операции для этой инструкции FF E4. Эта двухбайтовая последовательность может быть найдена по однобайтовому смещению от начала инструкции call DbgPrintпо адресу 0x7C941EED. Если злоумышленник перезаписывает адрес возврата программы этим адресом, программа сначала перейдет 0x7C941EED, интерпретирует код операции FF E4как jmp espинструкцию, а затем перейдет на вершину стека и выполнит код злоумышленника.

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

Этот метод также позволяет размещать шелл-код после перезаписанного адреса возврата на платформе Windows. Поскольку исполняемые файлы в основном основаны на адресе, 0x00400000а x86 - это архитектура Little Endian , последний байт адреса возврата должен быть нулевым, что завершает копирование буфера, и больше ничего не записывается. Это ограничивает размер шелл-кода размером буфера, который может быть чрезмерно ограничительным. Библиотеки DLL расположены в верхней памяти (см. Выше 0x01000000) и поэтому имеют адреса, не содержащие нулевых байтов, поэтому этот метод может удалить нулевые байты (или другие запрещенные символы) из перезаписанного адреса возврата. Используемый таким образом метод часто называют «трамплином DLL».

Защитные контрмеры

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

Выбор языка программирования

Ассемблер и C / C ++ - популярные языки программирования, которые уязвимы для переполнения буфера, отчасти потому, что они обеспечивают прямой доступ к памяти и не имеют строгой типизации . C не обеспечивает встроенной защиты от доступа или перезаписи данных в любой части памяти; более конкретно, он не проверяет, находятся ли данные, записанные в буфер, в пределах этого буфера. Стандартные библиотеки C ++ предоставляют множество способов безопасной буферизации данных, а стандартная библиотека шаблонов C ++ (STL) предоставляет контейнеры, которые при необходимости могут выполнять проверку границ, если программист явно вызывает проверки при доступе к данным. Например, vectorфункция-член at()a выполняет проверку границ и выдает out_of_range исключение, если проверка границ завершается неудачно. Однако C ++ ведет себя так же, как C, если проверка границ не вызывается явно. Для C. также существуют методы, позволяющие избежать переполнения буфера.

Строго типизированные языки, не допускающие прямого доступа к памяти, такие как COBOL, Java, Python и другие, в большинстве случаев предотвращают переполнение буфера. Многие языки программирования, отличные от C / C ++, обеспечивают проверку во время выполнения, а в некоторых случаях даже проверку во время компиляции, которая может отправлять предупреждение или вызывать исключение, когда C или C ++ перезаписывают данные и продолжают выполнять дальнейшие инструкции, пока не будут получены ошибочные результаты, которые могут или может не привести к сбою программы. Примеры таких языков включают Ada , Eiffel , Лисп , Modula-2 , Smalltalk , OCaml и такой C-производные в качестве циклона , ржавчины и D . В Java и .NET Framework среды байткодом также требуют проверки границ на всех массивах. Почти каждый интерпретируемый язык защищает от переполнения буфера, сигнализируя о четко определенном состоянии ошибки. Часто, когда язык предоставляет достаточно информации о типе для выполнения проверки границ, предоставляется опция для ее включения или отключения. Статический анализ кода может удалить многие проверки динамических привязок и типов, но плохие реализации и неудобные случаи могут значительно снизить производительность. Инженеры-программисты должны тщательно учитывать компромисс между безопасностью и затратами на производительность при принятии решения, какой язык и настройки компилятора использовать.

Использование безопасных библиотек

Проблема переполнения буфера обычна для языков C и C ++, поскольку они раскрывают низкоуровневые детали представления буферов как контейнеров для типов данных. Таким образом, следует избегать переполнения буфера, поддерживая высокую степень правильности кода, который выполняет управление буфером. Также давно рекомендуется избегать стандартных библиотечных функций, границы которых не проверяются, например gets, scanfи strcpy. Червь Morris эксплуатировал getsвызов в fingerd .

Хорошо написанные и протестированные библиотеки абстрактных типов данных, которые централизуют и автоматически выполняют управление буфером, включая проверку границ, могут уменьшить возникновение и влияние переполнения буфера. Двумя основными типами данных строительных блоков в этих языках, в которых часто происходит переполнение буфера, являются строки и массивы; таким образом, библиотеки, предотвращающие переполнение буфера в этих типах данных, могут обеспечить подавляющее большинство необходимого покрытия. Тем не менее, неправильное использование этих безопасных библиотек может привести к переполнению буфера и другим уязвимостям; и, естественно, любая ошибка в самой библиотеке является потенциальной уязвимостью. «Безопасные» реализации библиотеки включают «Лучшую библиотеку строк», Vstr и Erwin. Библиотека C операционной системы OpenBSD предоставляет функции strlcpy и strlcat , но они более ограничены, чем полные безопасные реализации библиотеки.

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

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

Защита от переполнения буфера используется для обнаружения наиболее распространенных переполнений буфера путем проверки того, что стек не был изменен при возврате функции. Если он был изменен, программа завершается с ошибкой сегментации . Три таких системы - это Libsafe и патчи gcc StackGuard и ProPolice .

Реализация Microsoft режима предотвращения выполнения данных (DEP) явно защищает указатель на обработчик структурированных исключений (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 также поддерживают защиту исполняемого пространства, называемую предотвращением выполнения данных . Фирменные дополнения включают:

  • BufferShield
  • StackDefender

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

Рандомизация разметки адресного пространства

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

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

Глубокая проверка пакетов

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

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

Тестирование

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

История

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

Самая ранняя задокументированная враждебная эксплуатация переполнения буфера произошла в 1988 году. Это была одна из нескольких эксплойтов, использованных червем Морриса для распространения через Интернет. Эксплуатируемая программа представляла собой службу в Unix под названием finger . Позже, в 1995 году, Томас Lopatic независимо друг от друга вновь открыли переполнение буфера и опубликовал свои выводы о 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 Exploit также используется переполнение буфера для достижения того же для PlayStation 2 . Взлом Twilight сделал то же самое с Wii , используя переполнение буфера в The Legend of Zelda: Twilight Princess .

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

использованная литература

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