Переполнение буфера, предотвращение выполнения данных и вы

Опубликовано: 9 Апреля, 2023


Введение


Я всегда называл это вариацией наживки и переключателя. Допустим, все, что вам нужно в этом мире, — это новый сверкающий 52-дюймовый телевизор с большим экраном, но вы знаете, что ваша жена будет сопротивляться этой идее. Что вы делаете? Что ж, вместо того, чтобы продвигать идею 52-дюймового телевизора, вы идете дальше и говорите ей, что присматриваетесь к 65-дюймовой плазме. Шансы на то, что все пройдет хорошо, практически равны нулю, но, сделав высокую подачу, у вас может быть больше возможностей для переговоров. Затем вы можете начать с бомбардировки информацией о преимуществах такого телевизора, а затем прокрасться в идею получения меньшего размера в качестве компромисса. Таким образом, она будет более склонна к идее 52″. Если бы вам это удалось, вы бы фактически забили ее голову такой чепухой, что вы могли бы проникнуть в идею, с которой хотите двигаться вперед!


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


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


Распознавание переполнения буфера


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


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


Изображение 23708
Рис. 1. Пример функции AC, уязвимой для переполнения буфера


Эта функция довольно проста и начинается с объявления двух переменных, bufferA и bufferB, которые имеют размеры 50 и 16 соответственно. Программа выводит пользователю вопрос, запрашивающий его имя, и использует функцию «получает», чтобы получить этот ввод. Наконец, данные, предоставленные пользователем, копируются из буфера А в переменную буфера, и функция завершается.


Для такой простой программы может показаться немного странным, что она уязвима для любых атак. Проблема здесь заключается в использовании функции «получает». Сама по себе функция «gets» не выполняет никакой проверки границ. Другими словами, он не гарантирует, что данные, вставленные в буфер A, на самом деле содержат 50 символов или меньше. Если пользователь введет более пятидесяти символов, программа вылетит.


Если вы следовали этому, то вы должны увидеть что-то еще, что выглядит странно. Функция strcopy копирует содержимое буфера A в буфер B. Однако буфер B имеет меньший размер, чем буфер A. Это означает, что даже если пользователь введет что-то меньше 50, но больше 16 символов в буфер A, это вызовет ситуацию переполнения при копировании в буфер B, что приведет к сбою программы. Эта небольшая программа имеет не одну, а ДВЕ уязвимости переполнения буфера.


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


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


Изображение 23709
Рисунок 2: Пример шелл-кода C и Assembly, предназначенного для возврата приглашения Windows C:


Код, который выполняется в этот момент, выполняется в контексте исходного уязвимого приложения пользователя. Это означает, что если программа запускается системным администратором, внедренный код также выполняется в контексте системного администратора. В зависимости от размера буфера злоумышленник может внедрить множество различных типов кодов. Безусловно, наиболее распространенным является так называемый шеллкод. Этот код вернет оболочку (например, приглашение Windows C:) человеку, который внедрил код. При правильном контексте пользователя это теперь означает, что этот человек теперь имеет полный контроль над рабочей станцией. Если вы не боялись переполнения буфера в этот момент, вы должны бояться сейчас. Переполнения буфера бывают разных форм и размеров, и тот, кто имеет опыт в «танцах стека», как это часто называют, имеет вполне реальную возможность получить полный контроль над любой уязвимой системой.


Предотвращение выполнения данных


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


DEP — это функция безопасности, изначально выпущенная в Windows XP SP2 и предназначенная для предотвращения выполнения кода приложения в неисполняемой области памяти. DEP доступен как в аппаратной, так и в программной конфигурации.


Аппаратный DEP


Использование аппаратной DEP считается наиболее безопасной реализацией DEP. В этой реализации процессор помечает все ячейки памяти как «неисполняемые», если только ячейка уже явно не содержит исполняемый код. Цель состоит в том, чтобы при этом DEP перехватывал попытки выполнения кода в неисполняемых областях.


Основная проблема с использованием аппаратного DEP заключается в том, что он поддерживается только минимальным количеством процессов. Функция процессора, которая позволяет это сделать, называется функцией NX для процессоров AMD и функцией XD для процессоров Intel.


Программная DEP


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


Настройка предотвращения выполнения данных


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


Изображение 23710
Рис. 3. Конфигурация DEP по умолчанию, показанная в Windows 7.


DEP имеет два доступных варианта конфигурации. Выбор по умолчанию показан на изображении выше и называется конфигурацией OptIn. Как указано в описании, этот параметр применяет DEP только к важным системным программам и службам. Этот уровень безопасности, называемый конфигурацией OptOut, определенно лучше, чем полное отсутствие DEP, но если вы хотите достичь наивысшего уровня безопасности, вам следует выбрать второй вариант, который применяет DEP ко всем программам и службам в системе. Вы также можете заметить, что нижняя часть этого экрана показывает, поддерживает ли используемый ЦП аппаратную DEP.


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


Изображение 23711
Рисунок 4: Настройка DEP для всех программ и одного исключения


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


Изображение 23712
Рисунок 5: DEP блокирует выполнение в проводнике Windows


Вывод


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


Если вы хотите узнать больше о переполнении буфера и его защите, я настоятельно рекомендую некоторые из следующих ресурсов:




  • Написание безопасного кода, Майкл Ховард и Дэвид Леблан


  • Взлом: искусство эксплуатации (2-е издание), Джон Эриксон


  • Разбивая стек ради удовольствия и прибыли, Алеф Один