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

Admin

Администратор

Переполнение буфера: Великая классика киберугроз и щиты современной ОС​



Переполнение буфера (Buffer Overflow) — это одна из самых старых и «результативных» уязвимостей в истории компьютерной безопасности. Несмотря на десятилетия борьбы, она до сих пор входит в топ самых опасных ошибок по версии CWE/SANS. В этой статье мы разберем механику атаки на стек и то, как современные операционные системы пытаются ей противостоять.


1. Механика атаки: Что происходит в памяти?​



Для понимания атаки нужно вспомнить, как работает Стек (Stack) — область памяти, где хранятся локальные переменные функций и, что самое важное, адреса возврата.



Как выглядит стек функции:​

  1. Локальные переменные: (например, массив char buffer[64]).
  2. Указатель кадра (EBP/RBP): служебная информация.
  3. Адрес возврата (Return Address): адрес инструкции, которую процессор должен выполнить после завершения функции.


Суть уязвимости​

Если программа использует «небезопасные» функции (например, gets(), strcpy() или scanf() в языке C), которые не проверяют размер вводимых данных, злоумышленник может подать на вход строку, превышающую размер буфера.



Процесс взлома:

  1. Атакующий заполняет буфер «мусором».
  2. Данные выходят за границы буфера и перезаписывают указатель кадра.
  3. Данные перезаписывают Адрес возврата.
  4. Вместо оригинального адреса атакующий подставляет адрес своего вредоносного кода (шеллкода), который он также внедрил в память.


Когда функция завершается, процессор «смотрит» на адрес возврата, видит там адрес шеллкода и начинает исполнять команды хакера (например, открытие командной строки с правами администратора).


2. DEP (Data Execution Prevention) — Запрет на исполнение​



Первым серьезным барьером стал DEP (в Linux известен как NX-бит — No-eXecute).



Принцип работы:

Раньше память была «перемешана»: в любой области можно было и хранить данные, и исполнять код. DEP разделяет области памяти. Стек и Куча (Heap) помечаются как только для данных.



  • Результат: Даже если хакер перезапишет адрес возврата и направит процессор на свой шеллкод в стеке, ОС мгновенно завершит программу с ошибкой, так как исполнение кода в этой области запрещено на уровне процессора.


Как обходят DEP?

С помощью техники ROP (Return-Oriented Programming). Атакующий не пишет свой код, а использует цепочки из маленьких фрагментов уже существующего кода самой программы (гаджетов), которые заканчиваются инструкцией возврата.

3. ASLR (Address Space Layout Randomization) — Игра в прятки​



Если DEP запрещает исполнять свой код, то ASLR делает невозможным нахождение чужого.



Принцип работы:

При каждом запуске программы операционная система случайным образом меняет базовые адреса:

  • Стека.
  • Кучи.
  • Библиотек (DLL или .so).
  • Самого исполняемого файла.


  • Результат: Атакующий не знает, по какому адресу находится функция system() или его гаджеты для ROP-цепочки. Попытка угадать адрес с вероятностью 99.9% приведет к крашу приложения.


Как обходят ASLR?

Обычно через утечки памяти (Memory Leaks). Если в программе есть другая уязвимость, позволяющая прочитать данные из памяти, хакер может вычислить смещение и узнать реальные адреса.


4. Stack Canaries — «Канарейка» в стеке​



Еще один метод защиты — Stack Canaries (назван в честь канареек, которых шахтеры брали в шахты для обнаружения газа).



Принцип работы:

Между локальными переменными и адресом возврата компилятор вставляет случайное число (куки). Перед тем как функция вернет управление, программа проверяет: «Жива ли канарейка?». Если буфер переполнился, канарейка будет затерта мусором. Программа увидит несовпадение значений и немедленно завершится до того, как управление перейдет к коду хакера.


Резюме​



Сегодня эксплуатация переполнения буфера превратилась в сложнейшее искусство. Хакеру нужно:

  1. Найти утечку памяти, чтобы обойти ASLR.
  2. Построить сложную цепочку ROP-гаджетов, чтобы обойти DEP.
  3. И при этом не задеть «канарейку».


Именно поэтому современные безопасные языки программирования (Rust, Go, Swift) стараются вообще исключить возможность прямой работы с памятью, которая есть в C/C++.