На главную | Содержание | Назад | Вперёд
Наши друзья

 

 

Переполнения буфера и программы на C++

ДЛЯ управления классами в языке C++ используются специальные структуры, которые могут служить для внедрения кода в систему. Хотя в классе C++ может быть перезаписано любое значение (что считается уязвимым местом), но чаще используется таблица указателей на функции vtable.
Таблицы vtable
В таблицах vtable хранятся указатели функций для класса. В каждом классе есть свои собственные функции (функции экземпляра) и они могут изменяться в зависимости от наследования. Это свойство называется полиморфизмом. Для хакера важно только то, что во vtable хранятся указатели функций. Если хакер сможет переписать эти указатели, он получит контроль над системой. На 16 продемонстрировано переполнение буфера в объекте класса. Переменные экземпляра наследуются из таблицы vtable в родительском классе, поэтому для атаки на переполнение буфера хакер должен попытаться воспользоваться чем-то другим. Хакер может создать деструктор, указывающий обратно на переменные экземпляра, которые он контролирует, — удачное место для хранения вредоносных команд.
Вредоносные данные
Обычно размер вредоносных данных, предназначенный для проведения атаки на переполнение буфера, довольно ограничен. В зависимости от вида программы атаки, размер вредоносных данных может быть сильно ограничен. К счастью, код для командного интерпретатора может быть весьма небольшим по размеру. Большинство современных программистов пользуются высокоуровневыми языками программирования и поэтому зачастую не знают, как выглядит их программа на машинном коде. Однако большинство хакеров применяют средства "ручной сборки" для создания кода командного интерпретатора. Для пояснения базовых принципов мы воспользу­емся машинным кодом для процессоров Intel семейства х86.
Хотя программный код на языке высокого уровня должен быть скомпилирован (как правило, в ущерб эффективности) в машинный код, хакер способен создать вручную намного более сжатый и эффективный код. При этом у хакера появляется несколько преимуществ, первое из которых касается размера программного кода. Используя написанные вручную команды, хакер может создавать очень "компактные" программы. Во-вторых, если есть ограничение относительно количества доступных для использования при атаке байтов (например, при наличии установленных фильтров), то подобный код позволяет обойти это ограничение. При использовании стандартного компилятора достичь этого не удастся.
В этом разделе мы рассмотрим примеры полезной нагрузки (точнее, вредоносных данных). Эту нагрузку можно мысленно разделить на нескольких частей, которые используются для описания концепций проведения атак. При этом мы предполагаем, что выбранный вектор вторжения позволяет хакеру добиться успеха и что указатель центрального процессора в режиме исполнения установлен на начало полезной нагрузки. Другими словами, мы начинаем с момента активизации полезной нагрузки и исполнения внедренного программного кода.
На 17 изображена структура стандартной полезной нагрузки. Прежде всего, нам нужно "сориентироваться на местности". Хакер создает небольшой фрагмент кода, который позволяет определить значение указателя команд. Другими словами, этот код позволяет выяснить, где в памяти размещается полезная нагрузка. Теперь нужно создать динамическую таблицу переходов (dynamic jump table) для всех внешних функций, которые мы планируем использовать в программе атаки (разумеется, мы не хотим вручную программировать вызов сокета, когда мы просто можем использовать интерфейс сокета, который экспортируется из системной библиотеки DLL). Таблица переходов позволяет нам использовать любую функцию из любой системной библиотеки. Мы также оставили место для размещения "другого кода", содержимое которого предоставляем придумать нашим читателям. В этой части содержится программа атаки, которую хочет запустить хакер. И в самом конце содержится раздел данных, в котором могут быть записаны строки данных и другая информация.
Сведения о размещении в памяти
Прежде всего, для использования полезной нагрузки следует выяснить, где она размещается в памяти (провести "рекогносцировку"). Без этой информации мы не можем найти раздел с данными или таблицу переходов. Не забывайте, что наша полезная нагрузка загружается как один большой блок данных. Указатель команд в данный, момент установлен на начало этого блока. Если мы можем узнать значение этого указателя, то с помощью несложных арифметических операций мы выясним положение в памяти других частей нашей полезной нагрузки. Для определения текущего положения в памяти можно воспользоваться следующими командами.
call p.^LliC
RELOC: pop edi '/ Лоло-геик*- а ^йялт?; \?^v:i£z,^ ■.«,;'«=si!(s si^:
Команда call заставляет записать значение EIP в стек. Мы немедленно извлекаем это значение из стека и размещаем его в EDI. При ассемблировании кода эта команда преобразуется в следующий набор байтов. Е8 00 00 00 00 5F
В этой строке содержатся четыре нулевых байта. Главным препятствием при проведении атак на переполнение буфера являются нулевые байты, поскольку наличие байта NULL (как мы рассказывали выше) в большинстве случаев будет означать завершение операции по работе со строкой. Таким образом, в разделе "Определение положения в памяти" не должно содержаться никаких символов NULL.
Возможно, стоит попробовать использовать следующий код.
START:
jmp RELOC3
RELOC2:
pop edi
jmp AFTER_RELOC
Определение положения в памяти
Таблица переходов с фиксированными адресами
Другой код
Таблица переходов
Данные

REL0C3:
call REL0C2 AFTER_RELOC:
Для этого кода могут потребоваться некоторые пояснения. Читатели могли заметить, что дело тут в одном лишнем бите. Сначала осуществляется переход к RELOC3, а затем назад к RELOC2. Мы хотим, чтобы вызов перешел к области памяти до оператора вызова (call). Эта хитрость приведет к отрицательному значению смещения для байтов нашего кода, что устранит символы NULL. Мы добавляем дополнительные переходы, чтобы обойти эти хитрости. После занесения значения указателя команд в регистр EDI, мы "перепрыгиваем" в оставшуюся часть кода (AFTER RELOC).
В результате компиляции этого хитрого кода мы получили следующий набор байтов.
ев 03 5f ев 05 е8 f8 ff ff ff
Совсем неплохо. Правда, появилось четыре дополнительных байта по сравнению с первой версией, но действенность намного повысилась, поскольку мы удалили символы NULL.
Размер полезной нагрузки
Размер полезной нагрузки является очень важным фактором. Например, если нужно "протиснуться в узкий проход", который ограничен правилами протокола и вершиной стека, то в распоряжении хакера остается только 200 байт. Не так уж много места для размещения нагрузки. На счету каждый байт.
Согласно описанной выше схеме, полезная нагрузка включает динамическую таблицу переходов и большой блок кода, предназначенный для упорядочивания работы с этой таблицей. Обратите внимание, что при недостатке места мы можем сжать таблицу переходов и код для этой таблицы, просто жестко закодировав адреса всех вызовов функций, которые мы планируем использовать.

 

На главную | Содержание | Назад | Вперёд
 
Яндекс.Метрика