Защита от хакеров корпоративных сетей
Шрифт:
Эта глава разделена на две части: для новичка и для квалифицированного читателя. Если читатель знаком с переполнением буфера и понимает, как им можно воспользоваться, то он может пропустить раздел для новичка. Но в любом случае рекомендуется просмотреть материал, предназначенный для квалифицированного читателя. Некоторые из вошедших в него способов входили в арсенал средств злоумышленников, например использовались при создании червя Code Red.
Стек
Стек – абстрактная структура данных, устроенная по принципу LIFO «последний вошел – первый вышел» (last in, first out – LIFO). Наглядно стек может быть представлен
Стек играет важную роль при взаимодействии программ. При вызове функции программа временно сохраняет в нем адрес возврата, параметры вызываемой функции и ее локальные переменные. Стек позволяет программистам упростить передачу параметров вызываемой функции и доступ к ее локальным переменным. В основном стек подобен буферу, в котором хранится вся необходимая для работы функции информация. Стек создается при вызове функции и разрушается при ее завершении. Стек статичен в том смысле, что после его создания назначение и тип выделенной стеку памяти не меняются, хотя хранящиеся в стеке данные могут изменяться.
Примечание
Все приведенные в главе примеры откомпилированы в операционной системе Windows 2000 компилятором VC++ 6 SP5 (Msdn.microsoft.com), если об этом ничего не сказано. Для большей ясности и простоты при компиляции примеров использовалась возможность построения выполнимой версии программы с отключенными опциями оптимизации программного кода. Примеры дизассемблирования подготовлены с использованием дизассемблера IDA pro 4.18 (www.datarescue.com). Все примеры предполагают использование стандартного чипсета x86.
Рассматриваемые в главе стеки процессора Intel x86 инвертированы в том смысле, что области памяти с меньшими адресами находятся на «вершине» стека. Операция push перемещает указатель вершины стека ниже (проталкивает запись в стек), в то время как операция pop – выше (выталкивает данные из стека). Данные располагаются в области памяти, отведенной под стек, начиная со дна стека, то есть с его максимального адреса, по последовательно уменьшающимся адресам памяти. Отчасти этим объясняется переполнение буфера: при записи в буфер от младших адресов к старшим возможно затирание данных, ранее сохраненных в области памяти со старшими адресами, например подмена сохраненного в стеке содержимого расширенного регистра команд EIP (Extended Instruction Pointer). Адрес доступного верхнего элемента хранится в регистре-указателе стека ESP.
Ошибки и защита
Изучение языка ассемблера
Для того чтобы лучше понять устройство стека, нужно знать ассемблер. Прежде всего использование регистров для работы с данными стека. Как правило, при работе со стеком используются следующие три регистра:
• EIP (Extended Instruction Pointer) – расширенный регистр указателя инструкции. Содержимое регистра указывает на следующую исполняемую машинную команду (текущий программный код). При вызове функций содержимое регистра сохраняется в стеке для дальнейшего использования;
• ESP (Extended Stack Pointer) – расширенный регистр указателя вершины стека. Содержимое регистра указывает на вершину стека (текущее положение в стеке). Добавление данных в стек и их удаление из стека осуществляются командами push и pop или с помощью непосредственных операций над содержимым регистра указателя вершины стека;
• EBP (Extended Base Pointer) – расширенный регистр базового указателя (указателя основной точки стека). Во
В последующих секциях главы будет рассказано о записи локальных переменных в стек, использовании стека для передачи параметров функции, и показано, каким образом злоумышленник может воспользоваться переполнением буфера, чтобы выполнить злонамеренный код.
Большинство компиляторов в начале функции вставляют служебный программный код, который иногда называют прологом (prologue) функции. Назначение пролога, помимо всего прочего, – подготовить стек для работы функции. Часто именно эта часть программного кода сохраняет старое содержимое регистра EBP и загружает в него указатель текущего положения в стеке. После этих действий регистр EBP содержит указатель на вершину стека выполняющейся функции. Зная содержимое регистра EBP и добавляя к нему смещение, получают ссылку на размещенные в стеке данные. Обычно регистр EBP адресует переменные, хранимые в стеке.
Приведенный ниже пример простой программы с несколькими локальными переменными демонстрирует сказанное. Подробные комментарии в исходном тексте программы позволят читателю лучше понять, что она делает.
Пример программы
Приведенная на рис. 8.1 написанная на языке C программа (C-программа) очень проста. Она присваивает своим переменным некоторые значения.
Рис. 8.1. Пример простой программы, иллюстрирующий работу стека
В программе создаются три локальные переменные, которые будут помещены в стек: 15-байтовый буфер символов buffer и две целые переменные intl и int2. Во время инициализации главной функции программы этим переменным присваиваются значения, а по завершении своей работы программа возвращает 1. Несмотря на простоту, программа полезна для изучения машинного кода оттранслированной функции на языке C вместе с прологом, эпилогом и стеком. Рассмотрим дизассемблерный вид приведенной на рис. 8.1 программы, которая была скомпилирована как консольное приложение Windows в режиме построения окончательной версии Release. Дизассемблирование
Дизассемблирование приведенной на рис. 8.1 программы показывает, как компилятор решил несложную задачу определения, инициализации локальных переменных и записи их значений в стек. Результаты дизассемблирования приведены на рис. 8.2.
Из рисунка 8.2 видно, что в прологе функции _main компилятор сначала сохранил старое значение регистра EBP в стеке, а затем записал в EBP адрес вершины стека функции (текущее положение в стеке). Эти стандартные действия делаются для того, чтобы каждая функция использовала свой собственный стек. Большинство, если не все, функций выполняют подобные операции в начале, а обратные им – в конце, в заключительной части программы – эпилоге.
Дамп стека
Для того чтобы можно было просмотреть область стека, после его инициализации в отладчике была установлена точка прерывания. При просмотре стека видно, что в нем хранится в начале работы функции, и легче понять, что происходит со стеком в процессе ее выполнения. Дамп стека показан на рис. 8.3.