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

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Версия для печати больше не поддерживается и может содержать ошибки обработки. Обновите закладки браузера и используйте вместо этого функцию печати браузера по умолчанию.

Переполнение буфера (англ. Buffer Overflow) — явление, возникающее, когда компьютерная программа записывает данные за пределами выделенного в памяти буфера.

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

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

Переполнение буфера может вызывать аварийное завершение или зависание программы, ведущее к отказу обслуживания (denial of service, DoS). Отдельные виды переполнений, например переполнение в стековом кадре, позволяют злоумышленнику загрузить и выполнить произвольный машинный код от имени программы и с правами учетной записи, от которой она выполняется[3].

Известны примеры, когда переполнение буфера намеренно используется системными программами для обхода ограничений в существующих программных или программно-аппаратных средствах. Например, операционная система iS-DOS (для компьютеров ZX Spectrum) использовала возможность переполнения буфера встроенной TR-DOS для запуска своего загрузчика в машинных кодах (что штатными средствами в TR-DOS сделать невозможно).

Безопасность

Программа, которая использует уязвимость для разрушения защиты другой программы, называется эксплойтом. Наибольшую опасность представляют эксплойты, предназначенные для получения доступа к уровню суперпользователя или, другими словами, повышения привилегий. Эксплойт переполнения буфера достигает этого путём передачи программе специально изготовленных входных данных. Такие данные переполняют выделенный буфер и изменяют данные, которые следуют за этим буфером в памяти.[4]

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

Правильно написанные программы должны проверять длину входных данных, чтобы убедиться, что они не больше, чем выделенный буфер данных. Однако программисты часто забывают об этом. В случае, если буфер расположен в стеке и стек «растёт вниз» (например в архитектуре x86), то с помощью переполнения буфера можно изменить адрес возврата выполняемой функции, так как адрес возврата расположен после буфера, выделенного выполняемой функцией. Тем самым есть возможность выполнить произвольный участок машинного кода в адресном пространстве процесса. Использовать переполнение буфера для искажения адреса возврата возможно, даже если стек «растёт вверх» (в этом случае адрес возврата обычно находится перед буфером).[5]

Даже опытным программистам бывает трудно определить, насколько то или иное переполнение буфера может быть уязвимостью. Это требует глубоких знаний об архитектуре компьютера и о целевой программе. Было показано, что даже настолько малые переполнения, как запись одного байта за пределами буфера, могут представлять собой уязвимости.[6]

Переполнения буфера широко распространены в программах, написанных на относительно низкоуровневых языках программирования, таких как язык ассемблера, Си и C++, которые требуют от программиста самостоятельного управления размером выделяемой памяти. Устранение ошибок переполнения буфера до сих пор является слабо автоматизированным процессом. Системы формальной верификации программ не очень эффективны при современных языках программирования.[7]

Многие языки программирования, например, Perl, Python, Java и Nim, управляют выделением памяти автоматически, что делает ошибки, связанные с переполнением буфера, маловероятными или невозможными.[8] Perl для избежания переполнений буфера обеспечивает автоматическое изменение размера массивов. Однако системы времени выполнения и библиотеки для таких языков всё равно могут быть подвержены переполнениям буфера вследствие возможных внутренних ошибок в реализации этих систем проверки. В Windows доступны некоторые программные и аппаратно-программные решения, которые предотвращают выполнение кода за пределами переполненного буфера, если такое переполнение было осуществлено. Среди этих решений — DEP в Windows XP SP2,[9] OSsurance и Anti-Execute.

В гарвардской архитектуре исполняемый код хранится отдельно от данных, что делает подобные атаки практически невозможными.[10]

Краткое техническое изложение

Пример

Рассмотрим пример уязвимой программы на языке Си:

#include <string.h>

int main(int argc, char *argv[])
{
	char buf[100];
	strcpy(buf, argv[1]);
	return 0;
}

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

Следующая программа не подвержена данной уязвимости:

#include <string.h>

int main(int argc, char *argv[])
{
	char buf[100];
	strncpy(buf, argv[1], sizeof(buf));
	return 0;
}

Здесь strcpy заменена на strncpy, в которой максимальное число копируемых символов ограничено размером буфера.[11]

Описание

На схемах ниже видно, как уязвимая программа может повредить структуру стека.

Иллюстрация записи различных данных в буфер, выделенный в стеке

В архитектуре x86 стек растёт от бо́льших адресов к меньшим, то есть новые данные помещаются перед теми, которые уже находятся в стеке.

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

Если программа имеет особые привилегии (например, запущена с правами root), злоумышленник может заменить адрес возврата на адрес шелл-кода, что позволит ему исполнять команды в атакуемой системе с повышенными привилегиями.[12]

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

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

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

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

  • перезаписывая локальную переменную, находящуюся в памяти рядом с буфером, изменяя поведение программы в свою пользу.
  • перезаписывая адрес возврата в стековом кадре. Как только функция завершается, управление передаётся по указанному атакующим адресу, обычно в область памяти, к изменению которой он имел доступ.
  • перезаписывая указатель на функцию[13] или обработчик исключений, которые впоследствии получат управление.
  • перезаписывая параметр из другого стекового кадра или нелокальный адрес, на который указывается в текущем контексте.[14]

Если адрес пользовательских данных неизвестен, но он хранится в регистре, можно применить метод «trampolining» (с англ. — «прыжки на батуте»): адрес возврата может быть перезаписан адресом опкода, который передаст управление в область памяти с пользовательскими данными. Если адрес хранится в регистре R, то переход к команде, передающей управление по этому адресу (например, call R), вызовет исполнение заданного пользователем кода. Адреса подходящих опкодов или байтов памяти могут быть найдены в DLL или в самом исполняемом файле. Однако адреса обычно не могут содержать нулевых символов, а местонахождения этих опкодов меняются в зависимости от приложения и операционной системы. Metasploit Project, например, хранил базу данных подходящих опкодов для систем Windows (на данный момент она недоступна).[15]

Переполнение буфера в стеке не нужно путать с переполнением стека.

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

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

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

Уязвимость в продукте GDI+ компании Microsoft, возникающая при обработке изображений формата JPEG — пример опасности, которую может представлять переполнение буфера в куче.[16]

Сложности в эксплуатации

Действия с буфером перед его чтением или исполнением могут помешать успешному использованию уязвимости. Они могут уменьшить угрозу успешной атаки, но не полностью исключить её. Действия могут включать перевод строки в верхний или нижний регистр, удаление спецсимволов или фильтрацию всех, кроме буквенно-цифровых. Однако существуют приёмы, позволяющие обойти эти меры: буквенно-цифровые шелл-коды,[17] полиморфические,[18] самоизменяющиеся коды и атака возврата в библиотеку.[19] Те же методы могут применяться для скрытия от систем обнаружения вторжений. В некоторых случаях, включая случаи конвертации символов в Юникод, уязвимость ошибочно принимается за позволяющую провести DoS-атаку, тогда как на самом деле возможно удалённое исполнение произвольного кода.[20]

Предотвращение

Для того, чтобы сделать переполнение буфера менее вероятным, используются различные приёмы.

Системы обнаружения вторжения

С помощью систем обнаружения вторжения (СОВ) можно обнаружить и предотвратить попытки удалённого использования переполнения буфера. Так как в большинстве случаев данные, предназначенные для переполнения буфера, содержат длинные массивы инструкций No Operation (NOP или NOOP), СОВ просто блокирует все входящие пакеты, содержащие большое количество последовательных NOP-ов. Этот способ, в общем, неэффективен, так как такие массивы могут быть записаны с использованием разнообразных инструкций языка ассемблера. В последнее время крэкеры начали использовать шелл-коды с шифрованием, самомодифицирующимся кодом, полиморфным кодом и алфавитно-цифровым кодом, а также атаки возврата в стандартную библиотеку для проникновения через СОВ.[21]

Защита от повреждения стека

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

Существуют две системы: StackGuard и Stack-Smashing Protector (старое название — ProPolice), обе являются расширениями компилятора gcc. Начиная с gcc-4.1-stage2, SSP был интегрирован в основной дистрибутив компилятора. Gentoo Linux и OpenBSD включают SSP в состав распространяемого с ними gcc.[22]

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

Защита пространства исполняемого кода для UNIX-подобных систем

Защита пространства исполняемого кода может смягчить последствия переполнений буфера, делая большинство действий злоумышленников невозможными. Это достигается рандомизацией адресного пространства (ASLR) и/или запрещением одновременного доступа к памяти на запись и исполнение. Неисполняемый стек предотвращает большинство эксплойтов кода оболочки.

Существует два исправления для ядра Linux, которые обеспечивают эту защиту — PaX и exec-shield. Ни один из них ещё не включен в основную поставку ядра. OpenBSD с версии 3.3 включает систему, называемую W^X, которая также обеспечивает контроль исполняемого пространства.

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

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

Некоторые процессоры, такие как Sparc фирмы Sun, Efficeon фирмы Transmeta, и новейшие 64-битные процессоры фирм AMD и Intel предотвращают выполнение кода, расположенного в областях памяти, помеченных специальным битом NX. AMD называет своё решение NX (от англ. No eXecute), а Intel своё — XD (от англ. eXecute Disabled).[23]

Защита пространства исполняемого кода для Windows

Сейчас существует несколько различных решений, предназначенных для защиты исполняемого кода в системах Windows, предлагаемых как компанией Майкрософт, так и сторонними компаниями.

Майкрософт предложила своё решение, получившее название DEP (от англ. Data Execution Prevention — «предотвращение выполнения данных»), включив его в пакеты обновлений для Windows XP и Windows Server 2003. DEP использует дополнительные возможности новых процессоров Intel и AMD, которые были предназначены для преодоления ограничения в 4 Гб на размер адресуемой памяти, присущий 32-разрядным процессорам. Для этих целей некоторые служебные структуры были увеличены. Эти структуры теперь содержат зарезервированный бит NX. DEP использует этот бит для предотвращения атак, связанных с изменением адреса обработчика исключений (так называемый SEH-эксплойт). DEP обеспечивает только защиту от SEH-эксплойта, он не защищает страницы памяти с исполняемым кодом.[9]

Кроме того, Майкрософт разработала механизм защиты стека, предназначенный для Windows Server. Стек помечается с помощью так называемых «осведомителей» (англ. canary), целостность которых затем проверяется. Если «осведомитель» был изменён, значит, стек повреждён.[24]

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

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

Проблема переполнений буфера характерна для языков программирования Си и C++, потому что они не скрывают детали низкоуровневого представления буферов как контейнеров для типов данных. Таким образом, чтобы избежать переполнения буфера, нужно обеспечивать высокий уровень контроля за созданием и изменениями программного кода, осуществляющего управление буферами. Использование библиотек абстрактных типов данных, которые производят централизованное автоматическое управление буферами и включают в себя проверку на переполнение — один из инженерных подходов к предотвращению переполнения буфера.[25]

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

История

Переполнение буфера было понято и частично задокументировано ещё в 1972 году в публикации «Computer Security Technology Planning Study».[26] Самое раннее задокументированное злонамеренное использование переполнения буфера произошло в 1988 году. На нём был основан один из нескольких эксплойтов, применявшихся червём Морриса для самораспространения через Интернет. Программа использовала уязвимость в сервисе finger системы Unix.[27] Позднее, в 1995 году, Томас Лопатик независимо переоткрыл переполнение буфера и занёс результаты исследования в список Багтрак.[28] Годом позже Элиас Леви[англ.] опубликовал пошаговое введение в использование переполнения буфера при работе со стеком «Smashing the Stack for Fun and Profit» в журнале Phrack.[12]

С тех пор как минимум два известных сетевых червя применяли переполнение буфера для заражения большого количества систем. В 2001 году червь Code Red использовал эту уязвимость в продукте компании Microsoft Internet Information Services (IIS) 5.0,[29] а в 2003 году SQL Slammer заражал машины с Microsoft SQL Server 2000.[30]

В 2003 году использование присутствующего в лицензионных играх для Xbox переполнения буфера позволило запускать на консоли нелицензионное программное обеспечение без модификации аппаратных средств с использованием так называемых модчипов.[31] PS2 Independence Exploit также использовал переполнение буфера, чтобы достичь того же результата для PlayStation 2. Аналогичный эксплойт для Wii Twilight применял эту уязвимость в игре The Legend of Zelda: Twilight Princess.

См. также

Примечания

  1. Эриксон, 2010, 0x320 Переполнение буфера, с. 139.
  2. Wheeler, 2004, 6. Avoid Buffer Overflow, с. 71.
  3. Эриксон, 2010, 0x321 Переполнение буфера в стеке, с. 142.
  4. Эриксон, 2010, 0x300 Эксплойты, с. 135—139.
  5. "HP-UX (PA-RISC 1.1) Overflows" by Zhodiac (англ.). Phrack. Дата обращения: 8 декабря 2014. Архивировано 3 декабря 2014 года.
  6. "The Frame Pointer Overwrite" by klog (англ.). Phrack. Дата обращения: 8 декабря 2014. Архивировано 3 декабря 2014 года.
  7. Wheeler, 2004, 6.1. Dangers in C/C++, с. 71.
  8. Wheeler, 2004, 6.4. Other Languages, с. 80.
  9. 1 2 Data Execution Prevention (DEP) (англ.). vlaurie.com. Дата обращения: 8 декабря 2014. Архивировано 18 декабря 2008 года.
  10. Hacking Windows CE (англ.). Phrack. Дата обращения: 14 декабря 2014. Архивировано 3 декабря 2014 года.
  11. Переполнение буфера своими руками №2. Журнал Xakep. Дата обращения: 8 декабря 2014. Архивировано 11 декабря 2014 года.
  12. 1 2 "Smashing the Stack for Fun and Profit" by Aleph One (англ.). Phrack. Дата обращения: 8 декабря 2014. Архивировано 6 февраля 2013 года.
  13. CORE-2007-0219: OpenBSD's IPv6 mbufs remote kernel buffer overflow (англ.). securityfocus.com. Дата обращения: 8 декабря 2014. Архивировано 12 февраля 2012 года.
  14. Modern Overflow Targets (англ.). Packet Storm. Дата обращения: 8 декабря 2014. Архивировано 23 октября 2016 года.
  15. The Metasploit Opcode Database (англ.). Metasploit. Дата обращения: 15 мая 2007. Архивировано 12 мая 2007 года.
  16. Microsoft Technet Security Bulletin MS04-028 (англ.). Microsoft. Дата обращения: 8 декабря 2014. Архивировано из оригинала 4 августа 2011 года.
  17. Writing ia32 alphanumeric shellcodes (англ.). Phrack. Дата обращения: 14 декабря 2014. Архивировано 10 марта 2014 года.
  18. Polymorphic Shellcode Engine (англ.). Phrack. Дата обращения: 14 декабря 2014. Архивировано 11 декабря 2014 года.
  19. The advanced return-into-lib(c) exploits (англ.). Phrack. Дата обращения: 14 декабря 2014. Архивировано 14 декабря 2014 года.
  20. Creating Arbitrary Shellcode In Unicode Expanded Strings (англ.) (PDF). Help Net Security. Дата обращения: 8 декабря 2014. Архивировано 5 января 2006 года.
  21. Day, D.J.; Sch. of Comput., Univ. of Derby, Derby, UK; Zhengxu Zhao; Minhua Ma. Detecting Return-to-libc Buffer Overflow Attacks Using Network Intrusion Detection Systems (англ.) // IEEE. — 2010. — P. 172—177. — ISBN 978-1-4244-5805-9. — doi:10.1109/ICDS.2010.37.
  22. Wheeler, 2004, 6.3. Compilation Solutions in C/C++, с. 79.
  23. Features (англ.). Ubuntu. Дата обращения: 9 декабря 2014. Архивировано 8 августа 2019 года.
  24. Perla, Oldani, 2011, CHAPTER 6 Windows.
  25. Wheeler, 2004, 6.2. Library Solutions in C/C++, с. 73.
  26. Computer Security Technology Planning Study (англ.) (PDF). Computer Security Resource Center (CSRC). Дата обращения: 8 декабря 2014. Архивировано из оригинала 21 июля 2011 года.
  27. "A Tour of The Worm" by Donn Seeley, University of Utah (англ.). world.std.com. Дата обращения: 3 июня 2007. Архивировано 20 мая 2007 года.
  28. Bugtraq security mailing list archive (англ.). www.security-express.com. Дата обращения: 3 июня 2007. Архивировано 1 сентября 2007 года.
  29. eEye Digital Security (англ.). eEye Digital Security. Дата обращения: 3 июня 2007. Архивировано 25 июня 2007 года.
  30. Microsoft Technet Security Bulletin MS02-039 (англ.). Microsoft. Дата обращения: 8 декабря 2014. Архивировано из оригинала 7 марта 2008 года.
  31. Hacker breaks Xbox protection without mod-chip (англ.). gamesindustry.biz. Дата обращения: 3 июня 2007. Архивировано 27 сентября 2007 года.

Литература

  • Джеймс Фостер, Майк Прайс. Защита от взлома: сокеты, эксплойты, shell-код = Sockets, Shellcode, Porting, & Coding. — М.: Издательский Дом ДМК-пресс, 2006. — С. 35, 532. — 784 с. — ISBN 5-9706-0019-9.
  • Джон Эриксон. 0x320 Переполнение буфера // Хакинг: искусство эксплойта = Hacking: The Art of Exploitation. — 2-е издание. — СПб.: Символ-Плюс, 2010. — С. 139. — 512 с. — ISBN 978-5-93286-158-5.
  • David A. Wheeler. Chapter 6. Avoid Buffer Overflow // Secure Programming for Linux and Unix HOWTO. — 2004. — P. 71. — 188 p.
  • Enrico Perla, Massimiliano Oldani. CHAPTER 6 Windows // A Guide to Kernel Exploitation: Attacking the Core. — 2011. — P. 334. — 442 p. — ISBN 978-1-59749-486-1.

Ссылки

Библиотеки и другие средства защиты