Открыть сервис

Smashing the Stack for Fun and Profit

Smashing the Stack for Fun and Profit — это классическая статья по компьютерной безопасности, опубликованная в 1996 году в электронном журнале Phrack (издание, признанное нежелательным на территории РФ). Она считается основополагающим трудом, впервые подробно и доступно описавшим механизм эксплуатации уязвимости переполнения буфера (buffer overflow) на примере стека вызовов (call stack) в операционных системах семейства Unix. Авторами статьи являются Элиас Леви (Elias Levy, известный под псевдонимом Aleph One) и, по некоторым данным, его соавтор — хакер под псевдонимом «Mudge». Статья оказала огромное влияние на развитие как методов атак, так и методов защиты программного обеспечения.

История и контекст

К середине 1990-х годов уязвимости переполнения буфера были известны специалистам, но их эксплуатация считалась сложной, малопонятной и часто описывалась как «чёрная магия». Большинство публикаций ограничивались общими описаниями или демонстрацией на конкретных примерах без объяснения фундаментальных принципов. Статья «Smashing the Stack for Fun and Profit» стала первой работой, которая систематизировала знания, разложила по полочкам архитектуру стека, механизмы вызова функций и хранения локальных переменных, а затем показала, как, зная эти детали, можно перехватить управление программой.

Статья была опубликована в выпуске Phrack Magazine №49 (файл 14). В то время интернет только начинал распространяться, а системы безопасности Unix-подобных систем (Linux, BSD, Solaris) ещё не имели встроенных механизмов защиты от подобных атак. Публикация вызвала огромный резонанс: одни увидели в ней руководство для взлома, другие — учебник по написанию безопасного кода. В любом случае, она стала «библией» для целого поколения хакеров и специалистов по информационной безопасности.

Основные понятия, описанные в статье

Стек вызовов (Call Stack)

Статья начинается с подробного объяснения того, как работает стек вызовов в архитектуре x86. Стек — это область памяти, организованная по принципу LIFO (Last In, First Out — «последним пришёл — первым ушёл»). Он используется для:

  • Хранения адресов возврата из функций.
  • Передачи аргументов функциям.
  • Хранения локальных переменных.
  • Сохранения состояния регистров процессора.

Авторы вводят ключевые понятия: указатель стека (ESP — Extended Stack Pointer), указатель базы (EBP — Extended Base Pointer) и регистр EIP (Extended Instruction Pointer), который хранит адрес следующей выполняемой инструкции.

Фрейм стека (Stack Frame)

Каждый вызов функции создаёт на стеке новый фрейм. Фрейм содержит:

  1. Аргументы функции (передаются в обратном порядке — справа налево).
  2. Адрес возврата (return address) — адрес инструкции, которая должна выполниться после завершения функции.
  3. Сохранённый EBP (saved frame pointer) — значение EBP вызывающей функции.
  4. Локальные переменные вызываемой функции.

Локальные переменные располагаются в порядке их объявления, но адресация идёт вниз по стеку (к меньшим адресам). Это означает, что массив, объявленный как char buffer[64], занимает 64 байта на вершине стека (ближе к младшим адресам), а адрес возврата и сохранённый EBP находятся выше (по старшим адресам).

Переполнение буфера (Buffer Overflow)

Если программа копирует данные в буфер без контроля длины (например, с помощью функций strcpy(), gets(), sprintf()), то данные могут выйти за его границы и перезаписать соседние области стека. В классическом сценарии, описанном в статье, атакующий:

  1. Помещает в буфер больше данных, чем он может вместить.
  2. Эти данные перезаписывают сначала сохранённый EBP, а затем — адрес возврата.
  3. Вместо корректного адреса возврата в стек записывается адрес, указывающий на начало вредоносного кода (шеллкода — shellcode), который также помещается в тот же буфер.

Механизм атаки

Шеллкод (Shellcode)

Шеллкод — это небольшой фрагмент машинного кода, который выполняет определённые действия, чаще всего — запуск командной оболочки (shell). В статье приводится пример шеллкода для Linux/x86, который вызывает системный вызов execve("/bin/sh", NULL, NULL). Этот код должен быть написан так, чтобы не содержать нулевых байтов (\x00), так как многие строковые функции (например, strcpy) копируют данные до первого нулевого байта, и такой байт оборвёт передачу шеллкода.

Пошаговая последовательность атаки

  1. Подготовка буфера: Атакующий создаёт строку, состоящую из трёх частей:
  • NOP-слайд (NOP sled): длинная последовательность инструкций NOP (No Operation — 0x90), которые ничего не делают и просто переходят к следующей инструкции. Это увеличивает шанс попадания на шеллкод, если точный адрес неизвестен.
  • Шеллкод: собственно исполняемый код.
  • Новый адрес возврата: адрес, который будет записан поверх оригинального адреса возврата. Он должен указывать на середину NOP-слайда или на начало шеллкода.
  1. Передача данных: Строка передаётся уязвимой программе (например, через аргумент командной строки, переменную окружения или сетевой пакет).
  1. Переполнение: Функция копирует данные в буфер. Данные выходят за границы буфера и перезаписывают сохранённый EBP и адрес возврата.
  1. Перехват управления: Когда функция завершается, инструкция ret считывает из стека новый адрес возврата (который теперь указывает на NOP-слайд). Процессор переходит по этому адресу и начинает выполнять NOP-инструкции, пока не дойдёт до шеллкода.
  1. Исполнение: Шеллкод запускает командную оболочку. Атакующий получает контроль над системой.

Примеры уязвимых функций

Статья приводит примеры кода на C, демонстрирующие уязвимость:

  • void function(int a, int b, int c) { char buffer1[5]; char buffer2[10]; strcpy(buffer2, source); } — классический пример с переполнением локального массива.
  • int main(int argc, char **argv) { char buffer[512]; strcpy(buffer, argv[1]); } — уязвимость в программе, копирующей аргумент командной строки в буфер без проверки длины.

Влияние и последствия

Развитие методов защиты

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

  • Stack Canaries (канарейки стека): компилятор (например, GCC с флагом -fstack-protector) помещает специальное случайное значение (канарейку) между локальными переменными и адресом возврата. Если при переполнении канарейка изменяется, программа аварийно завершается до того, как будет выполнен шеллкод.
  • NX-бит (Non-Execute): аппаратная поддержка запрета выполнения кода в стеке. Современные процессоры позволяют пометить страницы памяти как неисполняемые, что делает классический шеллкод в стеке бесполезным.
  • ASLR (Address Space Layout Randomization): случайное размещение в памяти стека, кучи и библиотек. Это затрудняет предсказание адреса возврата.
  • Безопасные функции: замена strcpy на strncpy, gets на fgets и т.д., а также использование статического анализа кода.

Этическая и правовая дискуссия

Статья «Smashing the Stack for Fun and Profit» стала предметом споров. Одни считали её опасным руководством для злоумышленников, другие — важным образовательным материалом, который помог разработчикам понять, как писать безопасный код. В конечном итоге, именно такие публикации привели к тому, что безопасность стала неотъемлемой частью разработки программного обеспечения.

Современное состояние

Хотя классическая атака на стек стала гораздо сложнее из-за современных защит, принципы, описанные в статье, остаются актуальными. Переполнение буфера по-прежнему является одним из самых распространённых классов уязвимостей, но теперь атакующие используют более сложные методы: обход ASLR, эксплуатацию в куче (heap overflow), ROP-цепи (Return-Oriented Programming) и другие техники. Статья «Smashing the Stack for Fun and Profit» продолжает изучаться как в курсах по информационной безопасности, так и в рамках истории вычислительной техники.

Критика и ограничения

  • Устаревшие архитектурные допущения: Статья написана для 32-битных систем x86 с плоской моделью памяти. В 64-битных системах (x86-64) адресация и соглашения о вызовах (calling conventions) отличаются, что требует модификации шеллкода.
  • Отсутствие защиты: В статье не рассматриваются современные методы защиты (канарейки, NX, ASLR), что делает описанные атаки неработоспособными на современных системах без специальных усилий.
  • Упрощение: Статья предполагает, что атакующий знает точный адрес стека, что в современных условиях крайне маловероятно без дополнительной утечки информации.

Источники

  1. Aleph One. «Smashing the Stack for Fun and Profit». Phrack Magazine, Issue 49, File 14, 1996.
  2. Elias Levy. «Smashing the Stack for Fun and Profit» (переиздание). Phrack Magazine, 2000.
  3. Cowan, C., et al. «StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks». Proceedings of the 7th USENIX Security Symposium, 1998.
  4. One, A. «The Art of Exploitation». No Starch Press, 2004.
  5. «Buffer Overflow». OWASP (Open Web Application Security Project), 2023.
  6. «Stack Smashing». The Linux Kernel Documentation, 2022.

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →