Использование переполнения буфера стека
Канонический метод использования переполнения буфера на основе стека — перезапись адреса возврата функции указателем на данные, контролируемые злоумышленником (обычно в самом стеке). Это показано в следующем примере:
#include <string.h> void foo(char *bar) { char c12]; strcpy(c, bar); // no bounds checking } int main(int argc, char **argv) { foo(argv1]); return ; }
Этот код берет аргумент из командной строки и копирует его в локальную переменную стека . Это отлично работает для аргументов командной строки меньше 12 символов (как вы можете видеть на рисунке B ниже). Любые аргументы длиной более 11 символов приведут к повреждению стека. (Максимальное количество безопасных символов на единицу меньше размера буфера здесь, потому что в языке программирования C строки заканчиваются нулевым байтовым символом. Таким образом, для ввода из двенадцати символов требуется тринадцать байтов для хранения, за вводом следует нулевым байтом сигнального устройства. Нулевой байт затем заканчивается перезаписью области памяти, которая на один байт выходит за пределы буфера.)
Стек программы с различными входами:
A. — Перед копированием данных. |
Б. — «привет» — это первый аргумент командной строки. |
C. — «AAAAAAAAAAAAAAAAAAAA \ x08 \ x35 \ xC0 \ x80» — это первый аргумент командной строки. |
Обратите внимание на рисунок C выше, когда аргумент, превышающий 11 байт, передается в командной строке, перезаписывает данные локального стека, сохраненный указатель кадра и, что наиболее важно, адрес возврата. При возврате он выталкивает адрес возврата из стека и переходит на этот адрес (т. Е
Начинает выполнять инструкции с этого адреса). Таким образом, злоумышленник перезаписал адрес возврата указателем на буфер стека , который теперь содержит данные, предоставленные злоумышленником. При фактическом использовании переполнения буфера стека строка «A» вместо этого будет шеллкодом, подходящим для платформы и желаемой функции. Если у этой программы были особые привилегии (например, бит SUID, установленный для работы от имени суперпользователя ), злоумышленник мог использовать эту уязвимость для получения привилегий суперпользователя на затронутой машине.
Е. Начинает выполнять инструкции с этого адреса). Таким образом, злоумышленник перезаписал адрес возврата указателем на буфер стека , который теперь содержит данные, предоставленные злоумышленником. При фактическом использовании переполнения буфера стека строка «A» вместо этого будет шеллкодом, подходящим для платформы и желаемой функции. Если у этой программы были особые привилегии (например, бит SUID, установленный для работы от имени суперпользователя ), злоумышленник мог использовать эту уязвимость для получения привилегий суперпользователя на затронутой машине.
Злоумышленник также может изменить значения внутренних переменных, чтобы воспользоваться некоторыми ошибками. В этом примере:
#include <string.h> #include <stdio.h> void foo(char *bar) { float My_Float = 10.5; // Addr = 0x0023FF4C char c28]; // Addr = 0x0023FF30 // Will print 10.500000 printf("My Float value = %f\n", My_Float); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Memory map: @ : c allocated memory # : My_Float allocated memory *c *My_Float 0x0023FF30 0x0023FF4C | | @@@@@@@@@@@@@@@@@@@@@@@@@@@@##### foo("my string is too long !!!!! XXXXX"); memcpy will put 0x1010C042 (little endian) in My_Float value. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ memcpy(c, bar, strlen(bar)); // no bounds checking... // Will print 96.031372 printf("My Float value = %f\n", My_Float); } int main(int argc, char **argv) { foo("my string is too long !!!!! \x10\x10\xc0\x42"); return ; }
Известные примеры
- Blaster червь в 2003 году распространение эксплуатируя переполнение стека буфера в Microsoft DCOM службы.
- Есть несколько примеров Wii, позволяющих запускать произвольный код в немодифицированной системе. «Сумеречный хак», который включает в себя длинное имя лошади главного героя в The Legend of Zelda: Twilight Princess , и «Smash Stack» для Super Smash Bros. Brawl, который включает использование SD-карты для загрузки специально подготовленного файла в хранилище. редактор игровых уровней. Хотя оба могут использоваться для выполнения любого произвольного кода, последний часто используется для простой перезагрузки самой Brawl с внесенными изменениями .
Атака: переполнение буфера стека
Прежде чем углубляться в технические подробности о том, что такое переполнение стекового буфера и как оно работает, давайте рассмотрим простую для понимания аналогию:
Переполнение буфера стека, как и атака Боба, перезаписывает данные, которые разработчик не собирался перезаписывать, обеспечивая полный контроль над программой и её выходными данными.
Конференция Heisenbug 2020 Moscow
4–7 ноября, Онлайн, От 14 500 до 64 000 ₽
tproger.ru
События и курсы на tproger.ru
Итак, теперь давайте посмотрим на это в реальном мире. Взгляните на следующий фрагмент кода:
В приведённой выше функции мы видим, что массив символьного типа с именем создаётся с размером 64. Затем мы видим, что переменная равна 0, и функция вызывается с переменной в качестве аргумента. Наконец, мы видим оператор , который проверяет, не равно ли значение нулю. Очевидно, что нет, где в этом приложении переменная имеет значение, отличное от 0. Так как мы собираемся её изменить?
Что ж, давайте сначала посмотрим на документацию функции :
Определение функции gets ()
Описание багов в функции gets ()
Как видите, функция просто принимает пользовательский ввод. Однако функция не проверяет, действительно ли пользовательский ввод вписывается в структуру данных, в которой мы его храним (в данном случае это ), и, таким образом, мы можем переполнить структуру данных и повлиять на другие переменные и данные стека. Кроме того, поскольку мы знаем, что все переменные хранятся в стеке, и мы знаем, что представляет собой переменная (0), всё, что нам нужно сделать, — это ввести достаточно данных, чтобы перезаписать переменную . Давайте посмотрим на диаграмму:
ASCII-диаграмма переполнения буфера стека
Как видите, если злоумышленник просто вводит слишком много текста, он может перезаписать переменную и всё остальное в стеке, включая указатели возврата. Это означает, что если злоумышленник сможет взять под контроль стек программы, он сможет эффективно контролировать всю программу и заставить её делать то, что он хочет. Например, можно просто перезаписать указатель возврата функции в стеке на пользовательский, указывающий на вредоносную функцию.
4 ответа
Лучший ответ
— это массив символов, достаточно большой, чтобы вместить плюс терминатор . Вы не можете добавить к нему, потому что у него нет свободного места.
Простое решение — дать массиву явную длину, достаточную для обработки всего, что может добавить ваша программа.
10
John Kugelman
19 Сен 2019 в 16:32
Ошибка разрушения стека возвращается из-за механизмов защиты памяти, используемых компилятором. Вы можете отключить эту функцию, но это может привести к уязвимостям, которые используются так называемой атакой переполнения буфера стека (см. видео этого компьютерного файла на YouTube ).
Вы не объявляете массив символов с определенным размером, а потому, что инициализируете его с помощью строковый литерал размер массива символов устанавливается равным 18 байтам (17 для символов в и 1 для { {X2}}, так называемый нулевой символ, используемый для завершения строки). Из стандарта C99 6.4.5 / 5 «Строковые литералы — семантика» (как указано в ответе на этот вопрос):
Грубо говоря, компилятор знает, что в памяти должно быть зарезервировано только 18 байт для этого конкретного символьного массива, и, следовательно, запись в массив не должна изменять данные после 18-го байта (по соображениям безопасности). Когда вы объединяете строки, вы фактически пытаетесь записать более 18 байтов, что вызывает механизм безопасности, который, в свою очередь, приводит к ошибке.
Поскольку вы проверяете только числа от 1 до 1000 и только с первыми 11 простыми числами, вы знаете, что простое число будет иметь длину не более 3 символов. Вместе с это добавляет 5 дополнительных символов, следовательно, 18 + 5 = 23 символа. Таким образом, ваша проблема легко решается путем явного объявления как символьного массива размером , то есть . Что касается получения более общего отзыва о вашем коде, вы должны проверить сообщество проверки кода.
2
gst
20 Сен 2019 в 09:31
Определение разбивания стека, опубликованное по адресу: https://www.techopedia.com/ определение / 16157 / стек — разбив
Определение — что означает Stack Smashing?
* Разрушение стека — это форма уязвимости, когда стек приложения компьютера или ОС вынужденно переполняется. Это может привести к подрыву программы / системы и ее поломке.
Стек, схема «первым пришел — последним вышел», является формой буфера, содержащего промежуточные результаты операций внутри него. Чтобы упростить, разбить стек, поместив в стек больше данных, чем его вместимость. Опытные хакеры могут сознательно вводить избыточные данные в стек. Избыточные данные могут храниться в других переменных стека, включая адрес возврата функции. Когда функция возвращается, она переходит к вредоносному коду в стеке, который может повредить всю систему. Это влияет на соседние данные в стеке и приводит к сбою программы. *
Относительно:
А также
Вызов пытается добавить строку: «31» в массив, который является достаточно большим, чтобы содержать строку: «делится на». В результате массив переполняется. Такое переполнение является неопределенным поведением. В этом случае он повредил стек, вероятно, прямо там, где расположен какой-либо кадр стека или другая связь
1
user3629249
20 Сен 2019 в 03:47
Безопасность
Программа, которая использует уязвимость для разрушения защиты другой программы, называется эксплойтом. Наибольшую опасность представляют эксплойты, предназначенные для получения доступа к уровню суперпользователя или, другими словами, повышения привилегий. Эксплойт переполнения буфера достигает этого путём передачи программе специально изготовленных входных данных. Такие данные переполняют выделенный буфер и изменяют данные, которые следуют за этим буфером в памяти.
Представим гипотетическую программу системного администрирования, которая исполняется с привилегиями суперпользователя — к примеру, изменение паролей пользователей. Если программа не проверяет длину введённого нового пароля, то любые данные, длина которых превышает размер выделенного для их хранения буфера, будут просто записаны поверх того, что находилось после буфера. Злоумышленник может вставить в эту область памяти инструкции на машинном языке, например, шелл-код, выполняющие любые действия с привилегиями суперпользователя — добавление и удаление учётных записей пользователей, изменение паролей, изменение или удаление файлов и т. д. Если исполнение в этой области памяти разрешено и в дальнейшем программа передаст в неё управление, система исполнит находящийся там машинный код злоумышленника.
Правильно написанные программы должны проверять длину входных данных, чтобы убедиться, что они не больше, чем выделенный буфер данных. Однако программисты часто забывают об этом. В случае если буфер расположен в стеке и стек «растёт вниз» (например в архитектуре x86), то с помощью переполнения буфера можно изменить адрес возврата выполняемой функции, так как адрес возврата расположен после буфера, выделенного выполняемой функцией. Тем самым есть возможность выполнить произвольный участок машинного кода в адресном пространстве процесса. Использовать переполнение буфера для искажения адреса возврата возможно даже если стек «растёт вверх» (в этом случае адрес возврата обычно находятся перед буфером).
Даже опытным программистам бывает трудно определить, насколько то или иное переполнение буфера может быть уязвимостью. Это требует глубоких знаний об архитектуре компьютера и о целевой программе. Было показано, что даже настолько малые переполнения, как запись одного байта за пределами буфера, могут представлять собой уязвимости.
Переполнения буфера широко распространены в программах, написанных на относительно низкоуровневых языках программирования, таких как язык ассемблера, Си и C++, которые требуют от программиста самостоятельного управления размером выделяемой памяти. Устранение ошибок переполнения буфера до сих пор является слабо автоматизированным процессом. Системы формальной верификации программ не очень эффективны при современных языках программирования.
Многие языки программирования, например, Perl, Python, Java и Ada, управляют выделением памяти автоматически, что делает ошибки, связанные с переполнением буфера, маловероятными или невозможными.Perl для избежания переполнений буфера обеспечивает автоматическое изменение размера массивов. Однако системы времени выполнения и библиотеки для таких языков всё равно могут быть подвержены переполнениям буфера, вследствие возможных внутренних ошибок в реализации этих систем проверки. В Windows доступны некоторые программные и аппаратно-программные решения, которые предотвращают выполнение кода за пределами переполненного буфера, если такое переполнение было осуществлено. Среди этих решений — DEP в Windows XP SP2,OSsurance и Anti-Execute.
В гарвардской архитектуре исполняемый код хранится отдельно от данных, что делает подобные атаки практически невозможными.
2 ответа
Решение
Вы, кажется, перепутали а также , Похоже, вы выделяете место для первого, но то, что вы читаете, это второе.
Кстати, почему вы не используете вместо ?
Обновить
Нет. Определенно, все не в порядке. Вы все еще перезаписываете область памяти; перезапись может быть «в основном безвредной» сейчас и на этой конкретной платформе, но она все еще потенциально смертельна. В другом контексте та же самая ошибка, если только не была установлена какая-либо защита (к счастью, в настоящее время это происходит очень часто — но вы не можете рассчитывать на удачу!), Может позволить удаленному злоумышленнику получить контроль над вашей машиной. Если вы попытаетесь использовать более длинные строки, есть вероятность, что ваша программа «ОК» без стекового протектора снова вызовет segfault (или, для более сложных программ, выдаст неверные результаты или даже нанесет ущерб системе).
6
2012-11-15 16:14
Когда ваша линия достигает ваш может сканировать за пределами буфера, потому что буфер не имеет символа ‘\0’.
РЕДАКТИРОВАТЬ: И есть еще один момент, который я хотел бы сделать. Это один спорный, потому что есть аргументы в пользу обеих возможностей. Было бы лучше использовать буферные индексы на мой взгляд. Это заставило бы обращаться с циклом по-другому, что имхо легче сделать правильно. Кроме того, при индексации массива с , в случае недосадки вы получите скорее всего с этим, чем с подписанным интегралом. У нас был случай в нашем проекте недавно (система, которая работает с 1998 года), и я изменил одну переменную в коде на и поймал 2 ошибки недостаточного количества, которые были пропущены с самого начала.
EDIT2: еще один стилистический момент, который я хочу сделать. Избегайте пост-(in|de) создания в выражениях цикла, они очень часто приводят к незаметным ошибкам (как показано выше). Предварительное (в | де) создание не является проблемой. Причина, по которой после (в | де) создания проблемы, заключается в том, что когда вы мысленно анализируете выражение для его условий (продолжить или прервать), оно сразу становится недействительным, то есть в конце выражения, значения, которые вы использовали в своей голове чтобы увидеть результат больше не существует. Может быть, это только я, но за 25 лет, которые я программировал на C, было огромное количество раз, когда я получал неправильные циклы после (в | де) обработки, и это всегда исправлялось либо предварительным (в | де) обработкой или снятие приращения из логического выражения.
2
2012-11-15 16:20
Причины возникновения ошибки переполнения стекового буфера в Windows 10
Из-за появившегося уведомления программа перестает работать. Подобная проблема возникает как в случае с простыми приложениями, так и в ресурсоемких играх. В обеих ситуациях неполадки вызваны тем, что программа задействовала больше данных, чем вмещает ее буфер. Как правило, подобное происходит при майнинге криптовалюты, но и обычные пользователи тоже сталкиваются с такими ошибками.
Переполненным стековым буфером активно пользуются злоумышленники, которые средствами установленного приложения пытаются получить доступ к информации на компьютере. Таким образом, владелец ПК может даже не подозревать, что на устройстве работает вредоносное ПО, однако процесс компроментирования, оказывается, уже запущен.
Пример с канарейкой
Нормальное распределение буфера для архитектур x86 и других подобных архитектур показано в записи о переполнении буфера . Здесь мы покажем измененный процесс применительно к StackGuard.
Когда функция вызывается, создается кадр стека. Фрейм стека строится от конца памяти до начала; и каждый кадр стека помещается на вершину стека, ближайшую к началу памяти. Таким образом, переход от конца фрагмента данных в стековом фрейме изменяет данные, ранее введенные в стековый фрейм; и запуск с конца кадра стека помещает данные в предыдущий кадр стека. Типичный кадр стека может выглядеть, как показано ниже, с первым размещенным адресом возврата (RETA), за которым следует другая управляющая информация (CTLI).
(CTLI)(RETA)
В C функция может содержать множество различных структур данных для каждого вызова. Каждый фрагмент данных, созданный по запросу, помещается в кадр стека по порядку и, таким образом, упорядочивается от конца к началу памяти. Ниже представлена гипотетическая функция и ее стековый фрейм.
int foo() { int a; /* integer */ int *b; /* pointer to integer */ char c10]; /* character arrays */ char d3]; b = &a; /* initialize b to point to location of a */ strcpy(c,get_c()); /* get c from somewhere, write it to c */ *b = 5; /* the data at the point in memory b indicates is set to 5 */ strcpy(d,get_d()); return *b; /* read from b and pass it to the caller */ }
(d..)(c.........)(b...)(a...)(CTLI)(RETA)
В этой гипотетической ситуации, если в массив записано более десяти байтов или в массив символов , избыток будет переполняться в целочисленный указатель , затем в целое число , затем в управляющую информацию и, наконец, в адрес возврата. При перезаписи указатель заставляется ссылаться на любую позицию в памяти, вызывая чтение с произвольного адреса. Путем перезаписи RETA функцию можно заставить выполнять другой код (при попытке возврата), либо существующие функции ( ret2libc ), либо код, записанный в стек во время переполнения.
В двух словах, плохое обращение с и , например, неограниченная зЬгсру () вызывает выше, может позволить злоумышленнику контролировать программу путем воздействия на значения , присвоенные и непосредственно. Целью защиты от переполнения буфера является обнаружение этой проблемы наименее навязчивым способом. Это делается путем удаления того, что может быть небезопасным, и размещения своего рода натяжной проволоки или канарейки после буфера.
Защита от переполнения буфера реализована как изменение компилятора. Таким образом, защита может изменять структуру данных в кадре стека. Именно так обстоит дело в таких системах, как ProPolice . Автоматические переменные вышеупомянутой функции переупорядочиваются более безопасно: массивы и выделяются первыми в кадре стека, который помещает целочисленный и целочисленный указатели перед ними в памяти. Таким образом, кадр стека становится
(b...)(a...)(d..)(c.........)(CTLI)(RETA)
Поскольку невозможно переместить CTLI или RETA, не нарушив созданный код, используется другая тактика. Дополнительная информация, называемая «канарейкой» (CNRY), помещается после буферов в стековом фрейме. Когда буферы переполняются, канареечное значение меняется. Таким образом, чтобы эффективно атаковать программу, злоумышленник должен оставить определенное указание на свою атаку. Кадр стека
(b...)(a...)(d..)(c.........)(CNRY)(CTLI)(RETA)
В конце каждой функции есть инструкция, которая продолжает выполнение с адреса памяти, указанного RETA . Перед выполнением этой инструкции проверка CNRY гарантирует, что она не была изменена. Если значение CNRY не проходит тест, выполнение программы немедленно прекращается. По сути, как преднамеренные атаки, так и непреднамеренные ошибки программирования приводят к прерыванию программы.
Канарский метод добавляет несколько служебных инструкций для каждого вызова функции с автоматическим массивом, непосредственно перед всем распределением динамического буфера и после освобождения динамического буфера. Накладные расходы, возникающие при использовании этого метода, незначительны. Однако это работает, если только канарейка не останется неизменной. Если злоумышленник знает, что он там, и может определить ценность канарейки, он может просто скопировать ее с собой. Обычно это сложно сделать намеренно, и крайне маловероятно в непреднамеренных ситуациях.
Положение канарейки зависит от реализации, но всегда между буферами и защищенными данными. Различное положение и длина имеют разные преимущества.
Добавление тегов
Тегирование — это основанный на компиляторе или аппаратный (требующий тегированной архитектуры ) метод тегирования типа фрагмента данных в памяти, используемый в основном для проверки типов. Помечая определенные области памяти как неисполняемые, он эффективно предотвращает хранение исполняемого кода в памяти, выделенной для хранения данных. Кроме того, определенные области памяти могут быть помечены как невыделенные, что предотвращает переполнение буфера.
Исторически тегирование использовалось для реализации языков программирования высокого уровня; при соответствующей поддержке операционной системы тегирование также может использоваться для обнаружения переполнения буфера. Примером может служить аппаратная функция NX bit , поддерживаемая процессорами Intel , AMD и ARM .
Рыбные котлеты на пару диетические
Приготовить пп котлеты можно и на пару. Вы можете использовать как обычную пароварку, так и мультиварку.
- 300 грамм рыбы. Берем любую нежирную рыбу и измельчаем с помощью мясорубки. Диетический фарш готов.
- 1 небольшая луковица. Мелко шинкуем или натираем на терке.
- 1 яйцо.
- 2 столовые ложки отрубей. Это добавит клетчатки в наши рыбные паровые котлеты. Кстати, именно отрубями можно заменить муку в некоторых рецептах котлет.
- Соль и перец по вкусу.
Смешиваем все ингредиенты и даем фаршу немного времени, чтобы он настоялся. За это время отруби немного разбухнут, и вы сможете с легкостью сформировать котлетки. Готовятся такие пп котлеты в течение 15−20 минут.
Выполнение атаки через root
Ошибки кодирования обычно являются причиной переполнения buffer. Распространенные ошибки при разработке приложений, которые могут привести к нему, включают в себя неспособность выделить достаточно большие буферы и отсутствие механизма проверки этих проблем. Такие ошибки особенно проблематичны в языках C/C++, которые не имеют встроенной защиты от переполнения и часто являются объектами атак переполнения буфера.
В некоторых случаях злоумышленник внедряет вредоносный код в память, которая была повреждена из-за переполнения стекового буфера. В других случаях просто используют преимущества повреждения соседней памяти. Например, программа, которая запрашивает пароль пользователя для предоставления ему доступа к системе. В приведенном ниже коде правильный пароль предоставляет привилегии root. Если пароль неверный, программа не предоставляет юзеру привилегии.
В приведенном примере программа предоставляет пользователю привилегии root, даже если он ввел неверный пароль. В этом случае злоумышленник предоставляет вход, длина которого больше, чем может вместить буфер, создавая переполнение, перезаписывающего память целого числа pass. Поэтому, несмотря на неверный пароль, значение pass становится ненулевым, и злоумышленник получает права root.
Альтернативная защита
Одной из часто предлагаемых альтернатив являются связанные версии, которые записывают в максимальный размер целевого буфера. На первый взгляд это выглядит как идеальное решение. К сожалению, у этих функций есть небольшой нюанс, который вызывает проблемы. При достижении предела, если завершающий символ не помещается в последний байт, возникают серьезные сбои при чтении буфера.
В этом упрощенном примере видна опасность строк, не оканчивающихся нулем. Когда foo помещается в normal buffer, он завершается нулем, поскольку имеет дополнительное место. Это лучший вариант развития событий. Если байты в переполнения буфера на стеке будут в другом символьном buffer или другой печатаемой строке, функция печати продолжить чтение, пока не будет достигнут завершающий символ этой строки.
Недостаток заключается в том, что язык C не предоставляет стандартную, безопасную альтернативу этим функциям. Тем не менее имеется и позитив — доступность нескольких реализаций для конкретной платформы. OpenBSD предоставляет strlcpy и strlcat, которые работают аналогично функциям strn, за исключением того, что они усекают строку на один символ раньше, чтобы освободить место для нулевого терминатора.
Аналогично Microsoft предоставляет свои собственные безопасные реализации часто используемых функций обработки строк: strcpy_s, strcat_s и sprintf_s.
Использование безопасных альтернатив, перечисленных выше, является предпочтительным. Когда это невозможно, выполняют ручную проверку границ и нулевое завершение при обработке строковых буферов.
Краткое техническое изложение[править | править код]
Описаниеправить | править код
Рассмотрим более подробно случай переполнения буфера, расположенного в области стека. Это удобнее всего сделать с помощью примера программы на языке Си или C++. Пример ориентирован на архитектуру x86, но работает и на многих других.
Когда динамический буфер, представляющий собой автоматический массив, выделяется в функции, он создаётся на стеке во время вызова этой функции. В архитектуре x86 стек растёт от бо́льших адресов к меньшим (или справа налево, в приведённых ниже диаграммах), то есть новые данные помещаются перед теми, которые уже находятся в стеке. Здесь, представляет существующий стек, и — это некоторое новое значение, которое ЦП поместил в стек:
(NEWDATA)(DATA)(DATA)(…)
Записывая данные в буфер, можно осуществить запись за его границами и изменить находящиеся там данные. Когда программа вызывает подпрограмму, она помещает адрес возврата в стек, так что подпрограмма знает, куда возвращать управление после того, как она завершится:
(ADDR)(DATA)(DATA)(…)
Когда выделятся динамический буфер, стек растёт влево на размер буфера. Так, если функция начинается с объявления , результатом будет:
(.a………)(ADDR)(DATA)(DATA)(…)
В конце подпрограммы память, занятая буфером, освобождается, и вызывается операция . Она извлекает адрес возврата из стека и выполняет переход по этому адресу, возвращая управление туда, откуда была вызвана подпрограмма.
Предположим, что 10-байтный буфер предназначен для того, чтобы содержать данные, предоставляемые пользователем (например — пароль). Если программа не проверяет количество символов, которые были введены пользователем, и записывает 14 байт в буфер, эти лишние данные «лягут» поверх адреса возврата, перезаписывая его новыми данными. Таким образом, это изменит место, в которое будет передано управление, когда завершится подпрограмма, и с которого программа продолжит исполнение после этого.
Если пользователь не злонамерен и вводит более, чем 10 символов, добавочные данные будут скорее всего случайными. В таком случае вполне возможно, что адрес возврата будет указывать на область памяти, которая неподконтрольна текущей исполняемой программе. Это вызовет ошибку сегментации в UNIX-системах или аналогичную ошибку в других операционных системах.
Однако пользователь может подставить в качестве адреса возврата и некий правильный адрес. Это вызовет переход управления в любую точку программы по его выбору. В результате потенциально может быть выполнен любой произвольный код, который этот пользователь поместил в данную область памяти, с теми привилегиями, с которыми выполняется текущая программа.
Примерправить | править код
Рассмотрим следующую программу на языке Си. Скомпилировав эту программу, мы сможем использовать её для генерации ошибок переполнения буфера. Первый аргумент командной строки программа принимает как текст, которым заполняется буфер.
/* overflow.c - демонстрирует процесс переполнения буфера */ #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char buffer10]; if (argc < 2) { fprintf(stderr, "ИСПОЛЬЗОВАНИЕ: %s строка\n", argv]); return 1; } strcpy(buffer, argv1]); return ; }
Программу можно опробовать с несколькими разными строками. Строки размером в 9 или меньше символов не будут вызывать переполнение буфера. Строки в 10 и более символов будут вызывать переполнение, хотя это может и не приводить к ошибке сегментации.
Эта программа может быть переписана следующим образом, с использованием функции для предотвращения переполнения. Однако, следует учитывать, что простое отбрасывание лишних данных, как в этом примере, также может приводить к нежелательным последствиям, в том числе, при определённых условиях, к повышению привилегий. Как правило, требуется более тщательная обработка таких ситуаций.
/* better.c - демонстрирует, как исправить ошибку */ #include <stdio.h> #include <string.h> #define BUFFER_SIZE 10 int main(int argc, char *argv[]) { char bufferBUFFER_SIZE]; if (argc < 2) { fprintf(stderr, "ИСПОЛЬЗОВАНИЕ: %s строка\n", argv]); return 1; } strncpy(buffer, argv1], BUFFER_SIZE - 1); bufferBUFFER_SIZE - 1 = '\0'; return ; }