Переполнение буфера


Содержание

Обнаружено переполнение стекового буфера в данном приложении — как исправить?

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

Причины возникновения ошибки переполнения стекового буфера

При скачивании пиратских сборок Windows на различных «варезниках» и трекерах, пользователи всегда рискуют. В них злоумышленники могут прятать целый набор вредоносного кода . А когда вы установите ОС на компьютер, вирусы активируются и компьютер начинает вести себя странно. Такой способ манипулирования компьютером уже известен в сфере сетевой безопасности. Многие популярные черви и другого рода вирусы использовали переполнение буфера в своих целях .

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

Что делать, если обнаружена уязвимость в данном приложении

Если вы встречаете ошибку в определённой программе, попробуйте переустановить её. Но загружать пакет нужно с официальных источников . Или с надёжных сайтов. Также перед установкой убедитесь, что в ней нет вирусов и она безопасна для вашего компьютера. Сканирования встроенным в систему антивирусом вполне достаточно. Перезагрузите компьютер. Возможно такое явление случайность. И ваш ПК продолжит работать без сбоев.

Чистая загрузка ОС Windows

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

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

Итак, выполните последовательно ряд таких действий:

  1. Вам нужно открыть окно конфигурации, для этого нажмите WIN+R и запишите msconfig . Затем нажмите кнопку ENTER;

Конфигурация системы: службы Windows

  • В этом окошке выберите вкладку « Службы »;
  • Выберите пункт « Отключить всё » и «Не отображать службы»;

    Отключение всех служб Windows

  • Далее выберите вверху « Автозагрузка » и нажмите «Открыть диспетчер задач» (Windows 8.1 и выше);
  • Здесь вам нужно нажать на каждую программу и выбрать кнопку « Отключить »;
  • Затем вернитесь в конфигурационное окно и нажмите кнопку «Ок».
  • Перезагрузите свой ПК и проверьте, есть ли сейчас ошибка. Чтобы определить программу или процесс, который является причиной сбоя, необходимо включать по одной программе в автозагрузке и службах, затем следует перезагружать компьютер.

    Использование антивирусного ПО

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

    Если сбой не удаётся решить описанными выше методами, скорее всего у вас в компьютере появился вирус. Систему необходимо просканировать. Если вы загружали стороннюю антивирусную программу, то встроенный Windows Defender (Защитник) сейчас отключён. Так как два вируса не «уживаются» на одном ПК. Попробуйте найти Защитник через поисковую строку компьютера и активировать его.

    Запуск Защитника Windows

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

    Специализированный софт

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

    Что такое переполнение буфера и как его вызвать?

    Я слышал о переполнении буфера, и я хотел бы знать, как его вызвать.

    может кто-нибудь показать мне небольшой пример переполнения буфера? Новые(а для чего они используются?)

    11 ответов

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

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

    С точки зрения того, как вы могли бы запрограммировать его самостоятельно, это было бы просто:

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

    классический пример переполнения буфера:

    переполнение буфера в одиночку чаще всего не происходит намеренно. Чаще всего это происходит из-за так называемой ошибки «off-by-one». Это означает, что вы неправильно рассчитали размер массива на единицу-может быть, потому, что вы забыли учесть завершающий нулевой символ, или потому, что некоторые другие вещи.

    но его также можно использовать для некоторых злых вещей. Действительно, пользователь давно знал эту дыру, а потом вставляет скажем 70 символов, с последними, содержащими некоторые специальные байты, которые перезаписывают некоторый стек-слот — если пользователь действительно хитрый, он / она попадет в слот обратного адреса в стеке и перезапишет его, чтобы он прыгнул вперед в этот только что вставленный буфер: потому что то, что ввел пользователь, было не его именем, а его кодом оболочки, который он ранее скомпилировал и выгрузил. Этот будет просто казнен. Есть некоторые проблемы. Например, вы должны организовать, чтобы в этом двоичном коде не было «\n » (потому что gets прекратите читать здесь). Для других способов, которые мешают опасным строковым функциям, двоичный ноль проблематичен, потому что строковые функции перестают копировать туда в буфер. Люди использовали xor с двумя одинаковыми значениями для получения нуля тоже, без записи нулевого байта явно.

    Это классический способ сделать это. Но есть некоторые блоки безопасности, которые могут сказать, что такие вещи произошли, и другие вещи, которые делают стек неисполняемым. Но я думаю, что есть способ трюки получше, чем я только что объяснил. Какой-то парень ассемблер, наверное, мог бы сейчас рассказать вам длинные истории об этом:)

    Как избежать этого

    всегда используйте функции, которые также принимают аргумент максимальной длины, если вы не 100% уверен, что буфер достаточно большой. Не играйте в такие игры, как «о, число не будет превышать 5 символов» — однажды он потерпит неудачу. Помните, что одна ракета, где ученые сказали, что число не будет превышать какая-то величина, потому что ракета никогда не будет такой быстрой. Но когда-нибудь . —20был на самом деле быстрее, и в результате произошло переполнение целого числа, и ракета разбилась (речь идет об ошибке в Ариан-5, одна из самых дорогих компьютерных ошибок в истории).

    , а не получает использовать fgets . И вместо sprintf использовать snprintf где подходит и доступен (или просто стиль c++ вещи, такие как istream и прочее)

    в современной ОС linux вы не можете использовать переполнение буфера без дополнительного эксперимента. почему ? потому что вы будете заблокированы ASLR (рандомизация слоя стека адресов) и стек защитник в этом современном компиляторе GNU C. вы не сможете легко найти память, потому что память попадет в случайную память, вызванную ASLR. и вы будете заблокированы стек защитник при попытке переполнения программы.

    для в начале вам нужно поставить ASLR равным 0 значение по умолчанию-2

    в этом случае не о старом стиле переполнения буфера учебник вы можете получить из интернета. или Aleph one tutorial больше не будет работать в вашей системе.

    теперь давайте сделаем уязвимость программы для сценария переполнения буфера

    смотрит на функцию strcpy опасен без протектора стека, потому что функция без проверки, сколько байтов мы будем вводить. компилировать с помощью extra вариант — fno-stack-protector dan-mpreferred-stack-boundary=2 для взлета стека protector в вашей программе C

    buffer overflow c program with SUID root access scenatio теперь у нас есть сделать это. теперь давайте искать, сколько байтов нам нужно поместить в буфер, чтобы сделать ошибку сегментации программы

    вы видите, что нам нужно 404 байта, чтобы сделать ошибку сегментации программы (сбой) теперь, сколько байтов нам нужно перезаписать EIP ? EIP инструкция будет выполнена после. так хакер переписывает EIP к злой инструкции, что они хотят в двоичном SUID на программе. если программа в корне SUID, инструкция будет запущена в корневом доступе.


    программа получила код возврата ошибки сегментации. давайте введем больше байтов и посмотрим на регистр EIP.

    теперь вы можете сделать свой следующий шаг.

    Переполнение буфера — Buffer overflow

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

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

    Эксплуатируя поведение переполнения буфера является хорошо известная безопасность эксплуатации . Во многих системах, расположение памяти программы или системы в целом, хорошо определены. Посылая в данных , предназначенных , чтобы вызвать переполнение буфера, можно записать в области , известные держать исполняемый код и заменить его на наличие вредоносного кода , или выборочно перезаписывать данные , относящиеся к состоянию программы, поэтому в результате чего поведение , которое не было намерению составителя оригинальный программист. Буфера распространены в операционной системе код (ОС), так что можно сделать атаки , которые выполняют привилегии и получить неограниченный доступ к ресурсам компьютера. Знаменитый Моррис червь в 1988 году использовал это в качестве одного из методов атаки.

    Языки программирования , обычно связанные с переполнением буфера включают C и C ++ , которые не обеспечивают встроенную защиту от доступа или перезаписи данных в любой части памяти и не автоматически проверять , что данные записываются в массив (встроенный тип буфера) находится в пределах границы этого массива. Bounds проверка может предотвратить переполнение буфера, но требует дополнительного кода и времени обработки. Современные операционные системы используют различные методы для борьбы с вредоносным переполнением буфера, в частности , пути рандомизации раскладки памяти , или сознательно оставляя пространство между буферами и ищут действия , которые пишут в этих областях ( «канареек»).

    содержание

    Техническое описание

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

    пример

    В следующем примере , выраженном в C , программа имеет две переменные , которые являются смежными в памяти: 8 байт длиной буфер строки, A, и два байта тупоконечник целое число, B.

    Первоначально, не содержит ничего, кроме нулевых байтов, а B содержит номер 1979.

    имя переменной В
    значение [ Пустая строка ] 1979
    шестнадцатеричное значение 00 00 00 00 00 00 00 00 07 BB

    Теперь программа пытается сохранить заканчивающуюся нулем строку «excessive» с ASCII — кодировке в буфере А.

    «excessive» длиной 9 символов и кодирует 10 байт, включая нулевой символ, но может принимать только 8 байт. Будучи не в состоянии проверить длину строки, она также переписывает значение B:

    имя переменной В
    значение «Е» 'Икс' «С» «Е» 'S' 'S' 'я' 'V' 25856
    наговор 65 78 63 65 73 73 69 76 65 00

    Значение Б теперь непреднамеренно заменяется числом, сформированного из части строки символов. В этом примере «е», за которым следует нулевой байт стал бы 25856.

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

    Для того, чтобы предотвратить переполнение буфера из происходит в этом примере, вызов зЬгсра может быть заменен strlcpy , который принимает максимальную емкость ( в том числе нуля-терминации символа) в качестве дополнительного параметра , и гарантирует , что не больше , чем эта сумма данных записывается A:

    При наличии, strlcpy функции библиотеки предпочтительнее strncpy , не нуль-прекратить буфер назначения , если длина исходной строки является больше или равен размеру буфера (третьего аргумента , переданного функции), поэтому не может быть нулевым байтом и не может рассматриваться как действительная строка C-стиле.

    эксплуатация

    Методы , чтобы использовать уязвимость переполнения буфера зависит от архитектуры , от операционной системы и области памяти. Например, эксплуатация на куче (используется для динамически выделенной памяти), заметно отличается от эксплуатации на стеке вызовов .

    эксплуатация Стековая

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

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

    Если адрес из введенных пользователем данных , используемых для воздействия на переполнение буфера стека непредсказуема, используя стек переполнение буфера , чтобы вызвать удаленное выполнение кода становится намного сложнее. Один из методов , который может быть использован , чтобы использовать такое переполнение буфера называется «батутом». В этой технике, злоумышленник найдет указатель на уязвимый буфер стека, и вычислить местоположение их шелкоды относительно этого указателя. Затем, они будут использовать перезапись , чтобы перейти к инструкции уже в памяти , которая будет сделать второй прыжок, на этот раз по сравнению с указателем; что второй прыжок будет ветвиться исполнение в шеллкоде. Соответствующие инструкции часто присутствуют в большом коде. Проекта Metasploit , например, поддерживает базу данных подходящих опкодами, хотя в нем перечислены только те , что в ОС Windows операционной системы.

    Кучи на основе эксплуатации

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

    Microsoft «s GDI + уязвимость в обработке JPEGs является примером опасности переполнения кучи может представить.

    Барьеры на пути к эксплуатации

    Манипулирование буфера, которое происходит до того , как считываются или выполняются, может привести к выходу из строя попытки эксплуатации. Эти манипуляции могут уменьшить угрозу эксплуатации, но не может сделать это невозможным. Манипуляции могут включать в себя преобразование в верхний или нижний регистр, удаление метасимволов и фильтрацию из не- буквенно — цифровых строк. Тем не менее, существуют методы , чтобы обойти эти фильтры и манипуляции; буквенно — цифровой код , полиморфный код , самомодифицирующийся код и вернуться к Libc атаки . Одни и те же методы могут быть использованы , чтобы избежать обнаружения систем обнаружения вторжений . В некоторых случаях, в том числе , где код преобразуется в Unicode , угроза уязвимости была искажена в разоблачениях , как только отказ в обслуживании , когда на самом деле удаленное выполнение произвольного кода возможно.

    Практичность эксплуатации

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

    NOP санки техника

    NOP-салазки является старейшим и наиболее широко известным методом успешно эксплуатирует переполнение стека буфера. Это решает проблему нахождения точного адреса буфера, эффективно увеличивая размер целевой области. Чтобы сделать это, гораздо более широкие слои стека повреждены с не-оп машинной команды. В конце данных злоумышленника , поставляемые, после того, как инструкции не-оп, злоумышленник размещает инструкции для выполнения относительного перехода к верхней части буфера , где Шеллкод находится. Эта коллекция не-опс упоминается как «NOP-Сани» , потому что , если обратный адрес переписывается с любым адресом в пределах не-оп области буфера, выполнение не будет «слайд» вниз не-OPS , пока он перенаправляются к фактическому вредоносному коду скачка в конце. Этот метод требует от атакующего угадать , где на стеке NOP-салазки вместо сравнительно небольшого шеллкода.

    Из — за популярности этого метода, многие производители систем предотвращения вторжений будет искать для этого набора инструкций нет-оп машины в попытке обнаружить шеллкода в использовании. Важно отметить , что NOP-салазки не обязательно содержит только традиционные инструкции не оп машины; любая команда , которая не коррумпированное состояние машины до точки , где Шеллкод не будет работать может использоваться вместо аппаратного не помогали не-оп. В результате, это стало обычной практикой для эксплуатируют писатель составить не-оп салазки с произвольно выбранными командами , которые не имеют никакого реальное влияние на выполнении шелкодов.

    Хотя этот метод значительно повышает вероятность того, что атака будет успешной, это не без проблем. Бреши , используя эту технику , все еще должны полагаться на некоторое количество удачи , что они будут угадать смещения в стеке , которые находятся в пределах NOP упряжках области. Неправильное предположение, как правило , приводит к сбою целевой программы и может предупредить системный администратор к деятельности злоумышленника. Другая проблема заключается в том , что NOP упряжках требует гораздо большего объема памяти , в которой провести NOP упряжках достаточно большой , чтобы быть полезным. Это может быть проблемой , когда выделенный размер буфера затрагиваемого слишком мал , и текущая глубина стеки неглубокая (т.е. там не так много места от конца текущего кадра стека до начала стеки). Несмотря на свои проблемы, NOP-салазки часто единственный метод , который будет работать на данной платформе, окружающей среды или ситуации, и как таковой он все еще является важным методом.

    Переход к адресу, сохраненному в регистре техники

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

    На практике программа не может преднамеренно содержать инструкции , чтобы перейти к конкретному регистра. Традиционное решение найти непреднамеренный экземпляр подходящего опкода в определенном месте где — то в памяти программы. На рисунке Е на левой стороне вы можете увидеть пример такого непреднамеренного экземпляра i386 jmp esp инструкции. Опкод для этой команды FF E4 . Эта последовательность из двух байт может быть найдена в одной байте от начала инструкции call DbgPrint по адресу 0x7C941EED . Если злоумышленник перезаписывает возвратный адрес программы с этого адреса программа первого прыжка , чтобы 0x7C941EED , интерпретировать опкод FF E4 как jmp esp инструкции, а затем перейти к вершине стека и выполнить код злоумышленника.

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

    Этот метод также позволяет Шеллкоду быть размещен после перезаписи адреса возврата на платформе Windows. Поскольку исполняемые файлы в основном базируются на адреса 0x00400000 и x86 является Little Endian архитектуры, последние байты адреса возврата должны быть нулевыми, который завершает буфер копию и ничего не написано дальше. Это ограничивает размер шеллкода до размера буфера, который может быть чрезмерно ограничительным. Библиотеки DLL расположены в верхней памяти (выше 0x01000000 ) , и поэтому имеет адреса , не содержащие нулевые байт, так что этот метод может удалить нулевые байты (или другие запрещенные символы) от перезаписи обратного адреса. Используется таким образом, этот метод часто называют «DLL батутом».

    Защитные меры противодействия

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

    Выбор языка программирования

    Сборка и C / C ++ популярных языков программирования, которые уязвимы для переполнения буфера, отчасти потому , что они обеспечивают прямой доступ к памяти и не сильно типизированным. С не обеспечивает никакой встроенной защиты от доступа или перезаписи данных в любой части памяти; более конкретно, он не проверяет , что данные , записанные в буфер находится в пределах границ этого буфера. Стандарт C ++ библиотека предоставляет множество способов безопасно буферных данных, и C ++ ‘s Standard Template Library (STL) предоставляет контейнеры , которые могут при необходимости выполнять проверку границ , если программист явно требует проверки при доступе к данным. Например, vector «функция с членом at() выполняет проверку оценки и генерирует out_of_range исключение , если границы проверить не удается. Однако, C ++ ведет себя так же , как C , если границы проверки не вызывается явно. Методы , чтобы избежать переполнения буфера также существуют для C.

    Языки, которые сильно типизированных и не позволяют прямого доступа к памяти, такие как COBOL, Java, Python и другие, для предотвращения переполнения буфера возникновения в большинстве случаев. Кроме C / C Многие языки программирования ++ обеспечивают проверку выполнения , а в некоторых случаях даже проверки времени компиляции , который может послать предупреждение или поднять исключение , когда C или C ++ будет перезаписывать данные и продолжить выполнять дальнейшие инструкции , пока не будут получены ошибочные результаты , которые могут или не может привести к взрыву программы. Примеры таких языков включают Ada , Eiffel , Лисп , Modula-2 , Smalltalk , OCaml и такой C-производные в качестве циклона , ржавчины и D . В Java и Framework .NET среда байткод также требует проверки границ на все массивы. Почти каждый интерпретируемый язык будет защищать от переполнения буфера, сигнализация четко определенных ошибок. Часто , когда язык предоставляет достаточно информации о типе , чтобы сделать проверку границ вариант предусмотрен для включения или отключения его. Статический анализ коды может удалить много динамической переплетенной и тип проверки, но плохие реализации и неудобные случаи могут существенно снизить производительность. Программные инженеры должны тщательно рассмотреть компромиссную безопасность по сравнению с затратами производительности при выборе языка и настройки компилятора для использования.

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

    Проблема переполнения буфера часто встречается в языках С и С ++ , потому что они показывают низкий уровень репрезентативные детали буферов в качестве контейнеров для типов данных. Переполнение буфера должен , таким образом , можно избежать за счет поддержания высокой степени корректности в коде , который выполняет управление буфером. Он также давно было рекомендовано избегать стандартных библиотечных функций , которые не проверяются границы, такие как gets , scanf и strcpy . Червь Morris эксплуатировал gets вызов в fingerd .

    Хорошо написанные и испытаны абстрактные библиотеки типов данных , которые централизовать и автоматически выполнять управление буфером, включая проверку границ, могут уменьшить возникновение и влияние переполнения буфера. Два основных типа многокомпонентного данных на этих языках , в которых на переполнение буфера часто происходят являются строки и массивы; Таким образом, библиотеки , предотвращающие переполнение буфера в этих типах данных могут обеспечить подавляющее большинство необходимого покрытия. Тем не менее, неспособность правильно использовать эти безопасные библиотеки может привести к переполнению буфера и другим уязвимостям; и , естественно, любая ошибка в самой библиотеке есть потенциальная уязвимость. «Безопасные» реализации библиотек включают «Струнный библиотека Better», встр и Эрвин. В OpenBSD операционной системы библиотека C обеспечивает strlcpy и strlcat функцию, но они более ограничены , чем полные реализации безопасных библиотек.


    В сентябре 2007 года Технический отчет 24731, подготовленный комитетом по стандартизации C, был опубликован; она определяет набор функций, которые основаны на строке стандартного C библиотеки и функции ввода / вывода, с дополнительными параметрами буфера размера. Однако эффективность этих функций с целью уменьшения переполнения буфера спорна; она требует вмешательства программиста на одного вызова функции основании, что эквивалентно интервенции, которые могли бы сделать аналогичный старые функции стандартной библиотеки переполнения буфера безопасности.

    Защита от переполнения буфера

    Защита от переполнения буфера используются для обнаружения наиболее распространенных переполнения буфера, проверяя , что стек не было изменено , когда функция возвращает. Если он был изменен, программа завершается с ошибкой сегментации . Три таких систем Libsafe, и Stackguard и ProPolice G патчей.

    Реализация Microsoft по Предотвращение выполнения данных режима (DEP) явно защищает указатель на Structured Exception Handler (SEH) от перезаписи.

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

    защита Pointer

    Переполнение буфера работы пути манипулирования указателей (включая хранимые адреса). PointGuard был предложен в качестве компилятора-расширения, чтобы предотвратить атакующую возможность надежно манипулировать указатели и адреса. Подход работает при наличии кода компилятор автоматически добавить в XOR-закодировать указатели до и после того, как они используются. Поскольку злоумышленник (теоретически) не знает, какое значение будет использоваться для кодирования / декодирования указателя, он не может предсказать, что он будет указывать на, если он переписывает его с новым значением. PointGuard не был выпущен, но Microsoft реализовала подобный подход, начиная с Windows XP SP2 и Windows Server 2003 SP1. Вместо того, чтобы осуществлять защиту указателя как функция автоматической, Microsoft добавила подпрограмму API, который можно назвать по усмотрению программиста. Это позволяет более высокую производительность (потому что она не используется все время), но налагает бремя на программиста, чтобы знать, когда это необходимо.

    Поскольку исключающий является линейной, злоумышленник может иметь возможность манипулировать кодированный указатель путем перезаписи только нижние байт адреса. Это может позволить нападение на успех, если атакующий может попытаться эксплоит несколько раз или может завершить атаку, вызывая указатель, чтобы указать на одно из нескольких мест (например, в любом месте в пределах салазок NOP). Microsoft добавила случайное вращение их схему кодирования, чтобы решить эту слабость к частичным перезаписям.

    Исполняемые защиты пространства

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

    Некоторые процессоры поддерживают функцию под названием NX ( «No Execute») или XD ( «Execute Disabled») бит, который в сочетании с программным обеспечением, может быть использован для обозначения страниц данных (например, те , которые содержат стек и кучу) в читаемой и записи , но не исполняемый файл.

    Некоторые операционные системы Unix (например , OpenBSD , MacOS ) корабль с исполняемой защитой пространства (например , W ^ X ). Некоторые дополнительные пакеты включают в себя:

    Новые варианты Microsoft Windows также поддерживают исполняемую защиту пространства, называемое предотвращение выполнения данных . Собственные дополнения включают в себя:

    Исполняемые защиты пространства обычно не защищает от возврата к-LIBC атак , или любой другой атаки , которая не опирается на выполнение кода нападавших. Тем не менее, на 64-битных системах , использующих ASLR , как описано ниже, исполняемая защита пространства делает его гораздо более трудным для выполнения таких атак.

    Адрес раскладка пространство рандомизации

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

    Случайность в виртуальной памяти адреса , при котором функции и переменные могут быть найдены может сделать использование переполнения буфера более трудно, но не невозможно. Это также заставляет нападавшего адаптировать попытку эксплуатации к индивидуальной системе, фольг попытки интернет — червей . Подобный , но менее эффективный метод является перебазирование процессов и библиотек в виртуальном адресном пространстве.

    Илон Маск рекомендует:  Faq состояние кнопки insert (insertoverwrite)

    Глубокая проверка пакетов

    Использование глубокого анализа пакетов (DPI) может обнаруживать, по периметру сети, очень основные удаленные попытки использовать переполнение буфера путем использования сигнатур атак и эвристики . Они способны блокировать пакеты , которые имеют подпись известной атаки, или если длинная серия Нет-Инструкции по эксплуатации (известная как NOP-салазки) обнаружена, они были когда — то использовали , когда расположение эксплоита полезной нагрузки немного переменное ,

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

    тестирование

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

    история

    Переполнение буфера было понято и частично публично задокументировано еще в 1972 году, когда в области компьютерной безопасности Изучение технологии планирование выложенного технику: «Код выполнения этой функции не проверяет адреса источника и назначения должным образом, позволяя части монитора будет перекрыто пользователь. Это может использоваться, чтобы ввести код в монитор, который позволит пользователю захватить контроль над машиной «. (Страница 61) Сегодня, монитор будет называться ядром.

    Самая ранняя документально враждебное использование переполнения буфера был в 1988 году был одним из нескольких эксплойтов , используемых червем Морриса распространять себя через Интернет. Программа эксплуатации была служба на Unix называется палец . Позже, в 1995 году, Томас Lopatic независимо друг от друга вновь открыли переполнение буфера и опубликовал свои выводы о Bugtraq списке рассылки безопасности. Через год, в 1996 году, Элиас Леви (также известный как Aleph One) опубликовал в Phrack журнале статью «Smashing стека для развлечения и прибыли», а введение шаг за шагом к эксплуатации стека на основе уязвимости переполнения буфера.

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

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

    Переполнение буфера

    Переполнение буфера (Buffer Overflow)— запись или чтение программой данных за пределами выделенного для этого в памяти буфера. Обычно возникает из-за неправильной работы с данными и памятью, при отсутствии жесткой защиты со стороны подсистемы программирования и ОС. Данный вид ошибок достаточно распространён и часто связан с опечатками при наборе. Существует также родственная ошибка — неполная обработка буфера (Buffer Underflow).

    Ошибки Buffer Overflow и Buffer Underflow буфера часто приводят к использованию в программе неинициализированных данных и как следствие к её неопределенному поведению. Переполнение буфера также может стать причиной ошибки сегментации (Access Violation).

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

    В таких распространённых языках, как C и C++, отсутствует встроенная проверка на границы чтения/записи данных. Это плата за возможность эффективной низкоуровневой работы с оперативной памятью. С другой стороны, практически все интерпретируемые языки и JIT среды (Java RTE, .NET Framework) имеют встроенную защиту от переполнения буфера.

    Для предотвращения использования уязвимости переполнения буфера во время исполнения программы существуют системы защиты от повреждения стека (Libsafe, StackGuard), системы защиты пространства исполняемого кода. Сложность же динамического обнаружения ошибок переполнения буфера связана с тем, что программа долгое время может работать стабильно, т.к. в неинициализированной памяти могут находиться приемлемые значения, а область перезаписываемой памяти может не использоваться. На этапе написания/отладки кода ошибки переполнения буфера и связанные с ними уязвимости могут быть обнаружены с помощью статического анализа.

    Вот несколько примеров данной ошибки, найденных в коде реальных open-source проектов с помощью статического анализатора PVS-Studio.

    Проект MAME (эмулятор игр). Выход за границы буфера.

    Массив ‘rawheader’ состоит из 108 байт. Планируется скопировать его содержимое, начиная с байта под номером 100. Беда в том, что при этом мы выйдем за границы массива. Можно скопировать только 8 байт. А копируется 20 байт.

    Теперь рассмотрим пример ошибки неполной очистки буфера (Buffer Underflow). Проект Chromium.

    Функция ZeroMemory очищает только часть структуры Exploded. Причина, что оператор ‘sizeof’ возвращает размер указателя. Чтобы исправить ошибку, необходимо разыменовать указатель.

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

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

    Переполнение буфера (buffer overflows) — название самой распространенной уязвимости в области безопасности программного обеспечения. Первая атака с применением данной уязвимости использовалась в вирусе-черве Морриса в 1988 году. С тех пор их число увеличивается с каждым годом. В настоящее время можно говорить, что уязвимости, связанные с переполнение буфера являются доминирующими при удаленных атаках, где обычный пользователь сети получает частичный или полный контроль над атакуемым хостом. Анализ атак и обнаруженных уязвимостей последних лет показывает, что данная проблема является первостепенной. Так, например, 9 из 13 выпусков CERT (Computer Emergency Response Team site) в 1998 году и по крайней мере половина выпусков 1999 года связаны с переполнением буфера [1]. Информационный обзор популярного списка рассылки Bugtraq показывает, что примерно 2/3 респондентов считает переполнение буфера основной причиной нарушения сетевой безопасности [2]. Отметим, что переполнение буфера присуще также программному обеспечению ряда аппаратных средств. Примером может служить уязвимость принтера HP LaserJet 4500 [20]. Очевидно, что эффективное решение данной проблемы позволит исключить большую долю самых серьезных угроз компьютерной безопасности.

    Основа атак с использованием этой уязвимости — принцип функционирования операционных систем, где программа получает привилегии и права запустившего ее пользователя или процесса. Таким образом, менее привилегированный пользователь или процесс, который взаимодействует с данной программой может использовать ее права в своих целях. Штатные средства программного обеспечения не позволяют выполнять такие действия. Однако “переполнение буфера” все же делает это возможным. Использование данной уязвимости подразумевает изменение хода выполнения привилегированной программы, например, запуск командной оболочки с правами администратора. Реализации атаки требует решения двух подзадач.

    · Подготовка кода, который будет выполнятся в контексте привилегированной программы.

    · Изменение последовательности выполнения программы с передачей управления подготовленному коду.

    Методы решения этих двух подзадач могут служить основой классификации атак, связанных с переполнением буфера [3].

    Рассмотрим пути решения подзадачи подготовки кода.

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

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

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

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

    Искажение адреса возврата из функции

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

    int namelen (void) <


    Из примера видно, что при вводе имени размером более 20 символов частью строки будет замещен адрес возврата из функции. Далее, при выполнении инструкции возврата из подпрограммы, управление будет передано по адресу, который образуют соответствующие позиции введенной строки и в обычной ситуации будет получено сообщение об ошибке операционной системы. Описанный процесс изображен на рисунке 1.

    Рисунок 1. Схема атаки “срыв стека”

    Такие атаки на переполнение буфера получили название “атаки срыва стека” (stack smashing attack) [4, 5, 6, 7].

    Искажение указателя функции

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

    int main(int argc, char **argv) <

    strcpy(buffer, argv[1]); // Уязвимость

    Здесь переполнение буфера buffer приводит к подмене указателя dummyptr и последующему изменению хода выполнения программы.

    Искажение таблиц переходов

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

    Искажение указателей данных

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

    Рассмотрим следующий фрагмент программы на C.

    char * p = arg; // уязвимый указатель

    char a[40]; // переполняемый буфер

    gets(a); // применение gets() реализует уязвимость

    gets(p); // искажение кода

    Здесь переполнение буфера a вызывает подмену указателя p и последующую запись строки по адресу искаженного указателя. Вводимая строка содержит код атакующего. Такая схема атаки часто используется для корректировки (patch) части кода программы или кода динамических и статических библиотек, располагающихся в памяти по фиксированным адресам. Например, корректировка-подмена системных функции выхода из программы или запуска процесса.

    Другой пример атаки подобного рода — искажение указателя кадра стека локальных переменных (frame pointer overwrite attack) [8]. Эта атака основана на стандартных операциях пролога и эпилога подпрограмм, в результате чего подменяется указатель базы кадра локальных переменных.

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

    Таблица 1. Классификация атак по переполнению буфера

    Подготовка кода Цель переполнения Внедрение кода Внедрение параметров Не требуется
    Искажение адреса возврата из функции Атака “срыв стека” Атака “срыв стека” с параметризацией Атака “срыв стека” с передачей управления
    Искажение указателей функций Атака на указатели функций Атака на указатели функций с параметризацией Атака на указатели функций с передачей управления
    Искажение таблиц переходов Атака на таблицы переходов Атака на таблицы переходов с параметризацией Атака на таблицы переходов с передачей управления
    Искажение указателей данных Атака с искажением указателей данных Атака с искажением указателей данных с параметризацией Атака с искажением указателей данных с оригинальным кодом

    Определив возможные виды атак по переполнению буфера рассмотрим методы защиты от них.

    Корректировка исходных кодов программы для устранения уязвимостей

    Переполнение буфера происходит прежде всего из-за неправильного алгоритма работы программы, который не предусматривает проверок выхода за границы буферов. Также особую роль здесь играет язык программирования Си и его стандартные библиотеки. Так как Си не содержит средств контроля соответствия типов, то в переменную одного типа можно занести значение другого типа. Стандартные функции Си такие как strcpy, sprintf, gets работают со строками символов и не имеют в качестве аргументов их размеров, что, как видно из приведенных выше примеров, легко приводит к переполнению буфера. Сложившийся годами стиль программирования более ориентированный на производительность программ, без выполнения дополнительных проверок также является причиной распространения данной уязвимости. В результате чего, для программистов выработано ряд методик и указаний по написанию программ не содержащих уязвимости [9]. Сформированы рекомендации по исправлению уже существующих программ (например, замена уязвимых функций: strcpy, spritnf на их аналоги strncpy, snprintf, в параметры которых входит размер строки). Созданы и постоянно возникают новые команды-объединения программистов по аудиту и исправлению кода существующих программ [10]. Существуют гибкие средства автоматически выполняющие действия имитирующие переполнение буфера на этапе отладки программы [11]. Также следует упомянуть об утилитах автоматического поиска уязвимостей в исходном коде программы. Указанные методы и средства позволяют создавать более защищенные программы, но не решают проблему в принципе, а лишь минимизируют число уязвимостей по переполнению буфера. К недостаткам следует отнести и то, что

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

    Использование неисполнимых буферов

    Суть метода заключается в запрещении исполнения кода в сегментах данных и стека, т.е. параметры сегментов данных и стека содержат только атрибуты записи и чтения, но не исполнения. Например, для реализации неисполняемого стека существуют “заплаты” для ОС Solaris и Linux [12, 13]. Однако ограничение на исполнение данных приводит к проблеме несовместимости. Исполняемый стек необходим для работы многим программам, так как на его основе генерируется код компиляторами, реализуются системные функции операционных систем, реализуется автоматическая генерация кода. Защита с использованием неисполнимых буферов предотвратит только атаки с внедрением кода, но не поможет при других видах атак.

    Применение проверок выхода за границы

    В основе данного метода лежит выполнение проверок выхода за границы переменной при каждом обращении к ней. Это предотвращает все возможные атаки по переполнению буфера, так как полностью исключает само переполнение. Проверки выхода за границы переменной опционально реализованы в некоторых компиляторах Си, например, Compaq C, cc в Tru64 Unix, cc в Alpha Linux [14]. Следует отметить, что реализованные проверки ограничены только точными ссылками на элементы массивов, но не производятся для указателей. Существует также “заплата” для gcc, которая позволяет компилировать программы с полностью реализованной (включая проверку указателей) проверкой выхода за границы массивов [15]. Однако, у этого решения есть существенный недостаток — значительное (до 30 раз) снижение производительности программы. Другие системы осуществляют проверки при доступе к памяти, выполняя вставки дополнительного объектного кода проверок во все места программы, где есть обращения к памяти [16]. Вставки могут производится как до сборки объектных файлов (Purify) так и после (Pixie). Такие проверки сказываются на производительности с ее уменьшением от 2 до 5 раз и скорее подходят для отладки.

    Применение проверок целостности

    Решение, основанное на данном методе, получено благодаря проекту Synthetix [17]. Цель Synthetix — специализация кода для увеличения производительности операционных систем. При этом вводится понятие так называемого квази-постоянства (Quasi-invariant), т.е. состояния среды, которое неизменно в определенных рамках. Такое квази-постоянство позволяет устранить ряд избыточного кода проверки выполнения различных условий. В рамках проекта реализован набор утилит, в том числе обеспечивающих контроль и защиту квази-постоянных состояний среды. К их числу относятся StackGuard и PointGuard [3, 18, 19]. StackGuard предназначен для защиты от всех атак по переполнению буфера с изменением адреса возврата из функции и реализован в виде “заплаты” к gcc. Данная заплата изменяет пролог и эпилог всех функций с целью проверки целостности адреса возврата из функции при помощи так называемого «canary word». Схема защиты изображена на рисунке 2.

    Измененный пролог каждой функции выполняет занесение в стек “canary word”, а эпилог проверку содержимого стека, занесенного ранее и, в случае, нарушения останавливает программу с предупреждающим сообщением. При атаке с искажением адреса возврата неизбежно произойдет искажение “canary word”, что и будет признаком нарушения целостности. Таким образом, целостность адреса возврата определяется целостностью “canary word”. В терминологии Synthetix, нарушение целостности является нарушением квази-постоянства среды. При известном значении “canary word” атакующий может организовать подмену адреса возврата без нарушения целостности. Поэтому “canary word” формируется StackGuard особым образом [19]:

    1. имеет значения 0, CR, LF, EOF, что не позволит провести атаку при переполнении буфера в библиотечных функциях Си, так как данные значения являются признаками конца строки;

    1. имеет псевдослучайное значение, генерируемое при каждом запуске программы.

    Авторами системы распространяется защищенная версия Red Hat Linux 5.1 [19], скомпилированная при помощи StackGuard.

    Продукт PointGuard предназначен для защиты от атак на указатели функций. Он также реализован в виде дополнения к компилятору gcc и осуществляет защиту путем помещения “canary word” перед каждым указателем функции и таблицей переходов. Существует ряд трудностей с реализацией данного алгоритма защиты [3]: 1) размещение “canary word” должно выполнятся одновременно с выделением памяти под переменную, 2) инициализация одновременно с инициализацией переменной, 3) проверка целостности должна производится при каждом обращении к защищаемой переменной. Поэтому PointGuard ограничивается лишь статическими указателями на функции, которые не являются агрегативными. В дальнейшем авторы намерены реализовать полнофункциональную версию, которая будет оперировать указателями на функции различных видов. PointGuard не сможет защитить от атак с искажением указателей данных. Хотя для исключения этих видов атак, программисту предоставляется возможность самому создавать переменные, из специальных защищенных классов.

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

    Механизм проверки целостности используется также другой системой защиты от атак по переполнению буфера — StackShield [23]. StackShield реализован в виде процессора ассемблерного кода, генерируемого gcc и выполняет защиту от атак с искажением адреса возврата и указателей функций. Для предотвращения подмены адреса возврата в прологе каждой функции выполняется сохранение этого адреса во вторичном (дополнительном) стеке, а эпилог восстанавливает его значение. В случае переполнения буфера и искажения адреса возврата он будет восстановлен эпилогом без выдачи дополнительных сообщений, что впоследствии может привести к аварийному завершению. Атака с подменой указателей функций пресекается путем вставки специального кода перед каждой инструкцией вызова подпрограммы по указателю. Специальный код выполняет проверку того, в каком сегменте расположен адрес, вызываемой подпрограммы. Если это область данных или стека то программа завершается с ненулевым кодом ошибки. Однако, при такой схеме защиты встает проблема несовместимости с программами, которые содержат исполняемый код в области данных и стека. Защита StackShield также практически не сказывается на производительности программы.

    Также следует отметить реализацию рассматриваемого метода для FreeBSD [21], которая выполнена в виде “заплат” с проверками целостности адреса возврата внутри базовой библиотеки libc. При этом защищаются только библиотечные функции, но не сама программа.

    Рассмотренные методы противодействия атакам по переполнению буфера не выполняют полную автоматическую защиту от всех возможных атак описанных в таблице 1. Ряд атак с искажением указателей данных носит логический характер и не могут быть выявлены в автоматическом режиме. Как ни странно, самая первая атака по переполнению буфера в вирусе-черве Морриса носила именно такой характер [3]. Программистам также следует обратить свой взор на языки, обеспечивающие проверку и сохранение типов, такие как Java и Паскаль, исключающие переполнение буфера. Однако, не следует забывать, что виртуальная машина Java написана на Си и, таким образом, может иметь уязвимости [22].

    Анализ уязвимости переполнения буфера

    Рубрика: Информационные технологии

    Дата публикации: 28.01.2020 2020-01-28

    Статья просмотрена: 483 раза

    Библиографическое описание:

    Сергеев Р. А. Анализ уязвимости переполнения буфера // Молодой ученый. — 2020. — №4. — С. 181-185. — URL https://moluch.ru/archive/138/38783/ (дата обращения: 12.11.2020).

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

    Ключевые слова: компьютерная безопасность, переполнение буфера, эксплойты

    Уязвимости переполнения буфера.

    Одной из самых опасных уязвимостей компьютерных программ является уязвимость переполнения буфера. Этот тип уязвимости характерен для программ, написанных на таких языках программирования, как C, C++, Ассемблер. Данные языки позволяют программисту задавать фиксированные размеры выделяемых областей памяти. В случае ошибки в обработке данных существует возможность выхода за пределы границ буферов. Следствием такого переполнения может быть как ситуация отказа в обслуживании (DoS), так и выполнение произвольного кода уязвимой программой, что означает компрометацию системы, исполняющей эту программу.

    Переполнение буфера в стеке.


    Рассмотрим простейшую программу на языке C (рисунок 1).

    Рис. 1. Уязвимая программа на языке C

    Данная программа принимает на входе строку, которую затем отображает на экран с помощью определенной функции display. Функция display, принимающая в качестве параметра строковый указатель, содержит уязвимость. В начале данной функции определяется ограниченный по размеру буфер buf. Далее, путем вызова функции strcpy, в локальный буфер копируется строка, на которую указывает параметр функции display. В данном месте и содержится уязвимость. Дело в том, что библиотечная функция strcpy не проверяет размеры буфера-приемника, копирование строки при этом продолжается до тех пор, пока в строке-источнике не встретиться нулевой символ, обозначающий конец строки. Таким образом, возможна ситуация переполнения локального буфера, если строка-источник, указатель на которую поступает в функцию display, будет достаточной длины.

    Рассмотрим, как выглядит переполнение со стороны отладчика. Снимки экрана с программой, запушенной под отладчиком Windbg, показаны на рисунках 2 и 3.

    Рис. 2. Начало функции display и состояние стека в момент входа

    Рис. 3. Состояние стека к моменту исполнения инструкции ret

    На рисунке 2 (до команды отладчика dd esp) представлен дизассемблированный код начала функции display. Видно, что по адресу 0040134d происходит выполнение команды lea, которая в данном случае загружает в регистр eax указатель на область памяти в стеке. Далее этот указатель используется в качестве параметра строки-приемника функции strcpy, которая получает управление после исполнения инструкции call по адресу 00401353 (через прыжок по адресу 00401с00).

    На рисунках 2 и 3 показаны два изображения состояния стековой области памяти, в момент входа в функцию display и перед самым выходом — исполнением инструкции ret. В данном случае программе передавалась строка, состоящая из 12h символов «A», четырех символов «B» и четырех символов «C». Хорошо видно, что важная управляющая информация — адрес возврата из функции — к моменту исполнения инструкции ret оказывается перезаписанной значением 43434343. Данное значение представляет собой четыре байта с ascii кодом символа «C». Таким образом, мы получаем здесь классическое переполнение буфера в стеке. Поскольку появляется возможность контролировать адрес возврата, то становится вероятным появление функциональности в уязвимой программе, которая не была запланирована ее разработчиком.

    Переполнение буфера не в стеке.

    Явление переполнение буфера может возникать не только в стековой памяти. Пусть у нас имеются некоторые переменные в программе (на языке C), которые объявлены как статические и не инициализированы. В том случае, если среди этих переменных есть буферы фиксированного размера, которые принимают входные данные без соответствующих проверок и объявляются перед некоторыми статическими указателями на функции, то возможно переполнение буфера в сегменте.bss. При перезаписи указателей на функции последующий их вызов способен привести к исполнению программой постороннего кода.

    Другим типом уязвимости переполнения является переполнение буфера, выделенного в куче [1, с. 119]. Куча — специальная область памяти, которую программа способна запрашивать динамически во время выполнения. Кроме того, что сами блоки памяти могут содержать важную для программы информацию, в выделенных из кучи участках содержатся служебные данные, перезапись которых может спровоцировать в конечном итоге выполнение произвольного кода.

    На некоторых системах [2, с. 291] при наличии переполнения используются особенности алгоритма управления памятью из кучи. Все свободные куски памяти (области, освобожденные с помощью функции free()) объединяются в двухсвязные списки. На рисунке 4 схематично показан фрагмент одного из таких списков.

    Рис. 4. Двухсвязный список свободных блоков кучи

    В заголовках свободных блоков в списке содержится информация о размере блока (size), размере предыдущего блока (prev_size) в случаях, если он свободный, указатель на следующий блок (fd), указатель на предыдущий блок (bk). Если при освобождении блока он граничит со свободным куском, то освобождаемый кусок будет слит с этим свободным куском. При этом возникает необходимость удаления свободного блока из двухсвязного списка, для этого функция free() вызывает макрос unlink(). Этот макрос имеет следующий вид:

    #define unlink(P, BK, FD) <

    Вначале в unlink() происходит извлечение указателей на следующий FD и предыдущий BK блоки из удаляемого из списка блока P. Далее, указателем предыдущего блока bk для последующего блока FD становится указатель на блок, предыдущий блоку P. Аналогично, указателем следующего блока fd для предыдущего блока BK становится указатель на блок, следующий за P блоком.

    Когда возникает переполнение в буфере, то возможна перезапись указателей fd и bk произвольными значениями. Фактически это означает, что макрос unlink() способен при своем выполнении записать 4 произвольных байта по произвольному адресу. В качестве адреса выбирается какой-либо элемент таблицы GOT, соответствующий функции, которая будет в дальнейшем вызываться программой. После этого возможно исполнение постороннего кода программой.

    Рассмотренные выше ситуации используются специальными программами, которые называются эксплойты. В соответствии с [2, с. 221], эксплойт — это «программа, которая использует уязвимость в программном обеспечении для выполнения заранее подготовленного кода».

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

    В самом простейшем случае шеллкод может разместиться в буфере уязвимой программы. Однако, в настоящее время, если в результате срабатывания триггера исполнение передастся в область стека, практически наверняка шеллкод не сработает, а программа завершится аварийно. Дело в том, что разработчики защитных механизмов программ и операционных систем давно обратили внимание на проблему эксплуатации программных уязвимостей. В результате были разработаны и разрабатываются в настоящий момент разнообразные техники и методы для противодействия и детектирования использования уязвимостей программного обеспечения. Так, в системе Windows эксплойт с шеллкодом в стеке, на который передается управление сразу после срабатывания триггера, не сработает по причине наличия технологии DEP — Data Execution Prevention. Данная технология использует (в случае поддержки со стороны процессора) NX/XD биты элементов директорий и таблиц страниц [3, с. 751] и предотвращает выполнение кода в секции данных, стеке и куче.

    Тем не менее, если эксплойт построен иным образом, возможен обход технологии DEP. Существует класс атак, основанных на использовании имеющегося в программе кода. К данным техникам можно отнести атаки типа ret-to-libc, возвратно-ориентированное программирование в целом. Эксплойт может иметь в своем составе часть, отвечающую за изменение атрибутов памяти (помечать ее как исполняемую) и копирование в эту область шеллкода. После скопированный код получит управление.

    Для противодействия таким случаям существует другая технология — Address Space Layout Randomization — рандомизация на уровне расположения в адресном пространстве. В результате исполняемые образы получают возможность загружаться в адресное пространство процесса по случайным (в определенном смысле) адресам, что делает атаки на основе возвратно-ориентированного программирования практически невозможными с использованием этих модулей.

    В целом, имеется множество различных деталей при эксплуатации уязвимостей повреждения памяти (переполнения буфера). В свою очередь, и технологии защиты не ограничиваются DEP и ASLR, могут принимать разнообразные формы — надстройки для компилятора, отдельные программы и др. Эксплойты, использующие уязвимости, представляют собой один из наиболее действенных способов компрометации безопасности компьютерных систем. Можно утверждать, что в настоящий момент вопросы эксплуатации уязвимостей и противодействия этому является актуальной областью компьютерной безопасности.

    1. Фостер Дж., Лю В. Разработка средств безопасности и эксплойтов / Пер. с англ. — М.: Издательство «Русская Редакция»; СПб.: Питер, 2007. — 432 стр.: ил.

    2. Скляров И. Программирование боевого софта под Linux / И. Скляров. — СПб.: БХВ-Петербург, 2007. — 416 с.: ил.

    3. Касперски К., Рокко Е. Искусство дизассемблирования / К. Касперски, Е. Рокко. — СПб.: БХВ-Петербург, 2008. — 891 с.: ил.

    Переполнение буфера

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

    в стеке…

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

    Илон Маск рекомендует:  Что такое код asp hcdynamiccompressionlevel

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

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

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

    Наконец, выше кадра стека находится только небо и звезды, пардон – свободное стековое пространство. Затирать тут особенно нечего и эта область памяти используется в основном для служебных нужд shell-кода. При этом следует учитывать, что: а) объем стека не безграничен и упирается в определенный лимит, так что выделять гигабайты памяти все-таки не стоит; б) если один из спящих объектов процесса-жертвы неожиданно проснется, содержимое свободной стековой памяти окажется искажено и чтобы этого не случилось, shell-код должен подтянуть регистр ESP к верхнему уровню, резервируя необходимое количество байт памяти; в) поскольку стековая память, принадлежащая потоку выделается динамически по мере его распухания, всякая попытка выхода за пределы сторожевой страницы (page guard) завершается исключением, поэтому либо не запрашивайте более 4 Кб, либо прочитайте хотя бы по одной ячейке из каждой резервируемой страницы, двигаясь снизу вверх. Подробнее об этом можно прочитать у Рихтера.

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

    За концом адреса возврата начинается область памяти, принадлежащая материнским функциям и содержащая: аргументы дочерней функции, автоматические переменные материнской функции, сохраненные регистры/кадр стека проматеринской функции/адрес возврата в праматеринскую функции и т. д. Теоретически переполняющийся буфер может все это затереть (ну бывают же такие буйные буфера), практически же – это либо ненужно, либо неосуществимо. Если мы можем навязать программе корректный адрес возврата (т. е. адрес возврата указывающий на shell-код или любую точку «родного» кода программы), то в материнскую функцию она уже не вернется и все махинации с материнскими переменными останутся незамеченными. Если же навязать корректный адрес возврата по тем или иным причинам невозможно, то материнская функция тем более не сможет получить управления.

    Намного большую информацию несет чтение материнской области памяти (см. «указатели и индексы» в предыдущей статье данного цикла) – здесь действительно можно встретить много чего интересного. Конфиденциальные данные (типа паролей и номеров кредитных карт), дескрипторы секретных файлов, которые невозможно открыть обычным образом, сокеты установленных TCP-соединений (почему бы их не использовать для обхода брандмаузеров?) и т. д.

    Модификация аргументов дочерней функции менее перспективна, хотя временами и бывает полезной. Среди аргументов Си/Си++ программ традиционно много указателей. Обычно это указатели на данные, но встречаются и указатели на код. Последние наиболее перспективны, поскольку позволяют захватывать управление программой до ее обрушения. Указатели на данные, конечно, тоже хороши (особенно те из них, что позволяют записывать по навязанным адресам навязанные данные, т. е. работают как Бейсик-функция POKE), однако, чтобы дотянуться до своих аргументов при последовательном переполнении уязвимого буфера, необходимо пересечь ячейки памяти, занятые адресом возврата…

    автоматические переменные дочерней функции

    [кадр стека материнской функции]

    адрес возврата в материнскую функци.

    аргументы дочерней функции

    автоматические переменные материнской функции

    [кадр стека праматеринской функции]

    адрес возврата в праматеринскую функци.

    аргументы материнской функции

    Рисунок 1 карта распределения стековой памяти

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

    Червь Love San решает проблему путем подмены адреса возврата на адрес машинной инструкции JMP ESP, расположенной во владениях операционной системы. Недостатки такой методики очевидны: во-первых, она не срабатывает в тех случаях, когда переполняющийся буфер расположен ниже вершины стека, а, во-вторых, местоположение инструкции JMP ESP тесно связано с версией операционной системы и получается как в той поговорке «за что боролись, на то и напоролись». К сожалению, более прогрессивных методик передачи управления пока не придумано…

    в куче…

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

    Допустим, в программе имеется структура demo, содержащая в том числе и буфер фиксированного размера:


    Неосторожное обращение с обрабатываемыми данными (например, отсутствие нужных проверок в нужном месте) может привести к возможности переполнения буфера buf и как следствие – затиранию расположенных за ним переменных. В первую очередь это переменные-члены самой структуры (в данном случае – переменная b), стратегия модификация которых вполне типична и подчиняется тем же правилам – общим для всем переполняющихся буферов. Менее очевидна возможность затирания ячеек памяти, лежащих за пределами выделенного блока памяти. Кстати, для буферов, монопольно владеющим всем выделенным блоком памяти, это единственно возможная стратегия переполнения вообще. Взгляните на следующий код. Как вы думаете, что здесь можно переполнить?

    Долгое время считалось, что переполнять здесь особенно и нечего. Максимум – можно устроить банальный DoS, но целенаправленно захватить управление жертвой невозможно в силу хаотичности распределения динамических блоков по памяти. Базовый адрес блока pвообще говоря случаен и за его концом может быть расположено все, что угодно, в том числе и невыделенный регион памяти, всякое обращение к которому приводит к немедленному исключению, аварийно завершающим программу.

    На самом деле, все это не более чем расхожие заблуждения. Сегодня переполнением динамических буферов никого не удивишь. Эта технология широко и небезуспешно используется в качестве универсального (!) средства захвата управления. Нашумевший червь Slapper – один из немногих червей, поражающий UNIX-машины – распространяется именно так. Как же такое возможно? Попробуем разобраться…

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

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

    Рассмотрим следующую организацию динамической памяти, при которой все выделяемые блоки соединены посредством двухсвязных списков, указатели на которых расположены в начале каждого блока (см. рис. «2»), причем смежные блоки памяти не обязательно должны находиться в соседних элементах списка, т. к. в процессе многократных операций выделения/освобождения список неизбежно фрагментируется, а постоянно дефрагментировать его себе дороже.

    указатель на следующий блок в цепочке

    указатель на предыдущий блок в цепочке

    память, выделенная блоку

    указатель на следующий блок в цепочке

    указатель на предыдущий блок в цепочке

    память, выделенная блоку

    Рисунок 2 карта приблизительного распределения динамической памяти

    Переполнение буфера приводит к затиранию служебных структур следующего блока памяти и как следствие – возможности их модификации. Но что это нам дает? Ведь доступ к ячейкам всякого блока осуществляется по указателю, возращенному программе в момент его выделения, а отнюдь не по «служебному» указателю, который мы собираемся затирать! Служебные указатели используются исключительно функциями malloc/free (и другим подобными им функциям). Искажение указателя на следующий/предыдущий блок позволяет навязать адрес следующего выделяемого блока, например, «наложив» его на доступный нам буфер, но никаких гарантий, что это получится у нас нет – при выделении блока памяти, функция malloc ищет наиболее подходящий с ее точки зрения регион свободной памяти (обычно это первый свободный блок в цепочке, совпадающий по размеру с запрошенным), и не факт, что наш регион ей подойдет. Короче говоря, не воодушевляющая перспектива получается.

    Освобождение блоков памяти – другое дело! Для уменьшения фрагментации динамической памяти функция free автоматически объединят текущий освобождаемый блок со следующим, если тот тоже свободен. Поскольку, смежные блоки могут находится на различных концах связывающего их списка, перед присоединением чужого блока памяти, функция free должна «выбросить» его из цепочки. Это осуществляется путем склейки предшествующего и последующий указателей, что в псевдокоде выглядит приблизительно так: *указатель на следующий блок в цепочке = указатель на предыдущий блок в цепочке. Постойте, но ведь в это… Да! Это аналог бейсик-функции POKE, позволяющий нам модифицировать любую ячейку уязвимой программы!

    Подробнее об этом можно прочитать в статье «Onceuponafree()…», опубликованной в 39h-номере электронного журнала PHRACK, доступного по адресу www.phrack.org. Статья перегружена техническими подробностями реализации динамической памяти в различных библиотеках и написана довольно тяжелым языком, но ознакомится с ней безусловно стоит.

    Как правило, возможность записи в память используется для модификации таблицы импорта с целью подмены некоторой API-функции гарантированно вызываемой уязвимой программой вскоре после переполнения («вскоре» потому что часы ее уже сочтены – целостность ссылочного каркаса динамической памяти нарушена и это неустойчивое сооружение в любо момент может рухнуть, пустив программу в разнос). К сожалению, передать управление на переполняющийся буфер скорее всего не удастся, т. к. его адрес наперед неизвестен и тут приходится импровизировать. Во-первых, злоумышленник может разместить shell-код в любом другом доступном ему буфере с известным адресом (см. «в секции данных…«). Во-вторых, среди функций уязвимой программы может встретиться и такие, что передают управление на указатель, переданный им с тем или иным аргументом (условимся называть такую функцию функцией f). После чего останется найти API-функцию, принимающую указатель на переполняющийся буфер и подменить ее адрес адресом функции f. В Си++ программах с их виртуальными функциями и указателями this такая ситуация случается не так уж и редко, хотя и распространенной ее тоже не назовешь. Но при проектировании shell-кода на универсальные решения закладываться вообще говоря и не приходиться. Проявите инженерную смекалку, удивите мир!

    Будьте заранее готовы к тому, что в некоторых реализациях кучи вы встретитесь не с указателями, а с индексами, в общем случае представляющие собой относительные адреса, отсчитываемые либо от первого байта кучи, либо от текущей ячейки памяти. Последний случай встречается наиболее часто (в частности, штатная библиотека компилятора MSVC 6.0 построена именно так), поэтому имеет смысл рассмотреть его поподробнее. Как уже говорилось выше, абсолютные адреса переполняющего буфера наперед неизвестны и непредсказуемым образом изменяются под воздействием ряда обстоятельств. Адреса же ячеек, наиболее соблазнительных для модификации, напротив, абсолютны. Что делать? Можно, конечно, исследовать стратегию выделения/освобождения памяти для данного приложения на предмет выявления наиболее вероятных комбинаций – кое-какие закономерности в назначении адресов переполняющимся буферам безусловно есть. Методично перебирая все возможные варианты один за другим, атакующий рано или поздно захватит управление сервером (правда, перед этим несколько раз его завесит, демаскируя атаку и усиливая бдительность администраторов).

    в секции данных…

    Переполняющиеся буфера, расположенные в секции данных (статические буфера) –настоящая золотая жила с точки зрения злоумышленника! Это единственный тип буферов, адреса которых явно задаются еще на этапе компиляции (вообще-то, не компиляции, а компоновки, но это уже детали) и постоянны для каждой конкретной версии уязвимого приложения независимо от того, на какой операционной системе она выполняется.

    Самое главное – секция данных содержит огромное количество указателей на функции/данные, глобальные флаги, дескрипторы файлов и кучи, имена файлов, текстовые строки, буфера некоторых библиотечных функций… Правда, до всего этого богатства еще предстоит «дотянуться» и, если длина переполняющегося буфера окажется жестко ограничена сверху (как часто и случается), атакующий не получит от последнего никаких преимуществ!

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

    секреты проектирования shell-кода

    Попытка реализовать собственный shell-код неминуемо натыкает атакующего на многочисленные ограничения, одни из которых обходятся путем хитроумных хаков и извращений, с другими же приходится мириться, воспринимая их как неотъемлемую часть жестоких сил природы.

    запрещенные символы

    Строковые переполняющиеся буфера (в особенности те, что относятся к консольному вводу и клавиатуре) налагают жесткие ограничения на ассортимент своего содержимого. Самое неприятное ограничение заключается в том, что символ нуля на всем протяжении строки может встречаться лишь однажды и лишь на конце строки (правда, это ограничение не распространяется на UNICODE-строки). Это затрудняет подготовку shell-кода и препятствует выбору произвольных целевых адресов. Код, не использующих нулевых байт, приятно называть ZeroFree кодом и техника его подготовки – настоящая Кама-Сутра.

    Искусство затирания адресов. Рассмотрим ситуацию, когда следом за переполняющимся буфером идет уязвимый указатель на вызываемую функцию (или указатель this), а интересующая злоумышленника функция root располагается по адресу 00401000h. Поскольку, только один символ, затирающий указатель, может быть символом нуля, то непосредственная запись требуемого значения невозможна и приходится хитрить.

    Начнем с того, что в 32-разрядных операционных системах (к которым, в частности, принадлежит Windows NT и многие клоны UNIX’а) стек, данные и код большинства приложений лежат в узком диапазоне адресов: 00100000h

    00×00000h, т. е. как минимум один ноль у нас уже есть – и это старший байт адреса. В зависимости от архитектуры процессора он может располагаться как по младшим, так и по старшим адресам. Семейство x86-процессоров держит его в старших адресах, что с точки зрения атакующего, очень даже хорошо, поскольку мы можем навязать уязвимому приложению любой XxYyZzh адрес, при условии, что Xx, Yy и Zz не равны нулю.

    Теперь давайте рассуждать творчески: позарез необходимый нам адрес 401000h в прямом виде недостижим в принципе. Но, может быть, нас устроит что-нибудь другое? Например, почему бы не начать выполнение функции не с первого байта? Функции с классическим прологом (коих вокруг нас большинство) начинаются с инструкции PUSH EBP, сохраняющей значение регистра EBP в стеке. Если этого не сделать, то при выходе функция непременно грохнется, но… это уже будет неважно (свою миссию функция выполнила и все, что было нужно атакующему она выполнила). Хуже, если паразитный символ нуля встречается в середине адреса или присутствует в нем дважды, например – 50000h.

    В некоторых случаях помогает способ коррекции существующих адресов. Допустим, затираемый указатель содержит адрес 5000FAh. Тогда, для достижения желаемого результата атакующий должен затереть один лишь младший символ адреса, заменив FAh символом нуля.

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

    Следует также учитывать, что некоторые функции ввода не вырезают символ перевода каретки из вводимой строки, чем практически полностью обезоруживают атакующих. Непосредственный ввод целевых адресов становится практически невозможным (ну что интересного можно найти по адресу 0AXxYyh?), коррекция существующих адресов хотя и остается возможной, но на практике встретить подходящий указатель крайне маловероятно (фактически мы ограничены лишь одним адресом ??000A, где ?? прежнее значение уязвимого указателя). Единственное, что остается – полностью затереть все 4-байта указателя вместе с двумя последующими за ним байтами. Тогда, мы сможем навязать уязвимому приложению любой FfXxYyZz, где Ff > 00h. Этот регион обычно принадлежит коду операционной системы и драйверам. С ненулевой вероятностью здесь можно найти машинную команду, передающую управление по целевому адресу. В простейшем случае это CALL адрес/JMP адрес (что достаточно маловероятно), в более общем случае – CALL регистр/JMP регистр. Обе – двухбайтовые команды (FF Dx и FF Ex соответственно) и в памяти таких последовательностей сотни! Главное, чтобы на момент вызова затертого указателя (а, значит, и на момент передачи управления команде CALL регистр/JMP регистр) выбранный регистр содержал требуемый целевой адрес.

    Штатные функции консольного ввода интерпретируют некоторые символы особым образом (например, символ с кодом 008 удаляет символ, стоящий перед курсором) и они [censored] еще до попадания в уязвимый буфер. Следует быть готовым и к тому, что атакуемая программа контролирует корректность поступающих данных, откидывая все нетекстовые символы или (что еще хуже) приводит их к верхнему/нижнему регистру. Вероятность успешной атаки (если только это не DoS атака) становится исчезающе мала.

    Подготовка shell-кода. В тех случаях, когда переполняющийся строковой буфер используется для передачи двоичного shell-кода (например, головы червя), проблема нулевых символов стоит чрезвычайно остро – нулевые символы содержатся как в машинных командах, так и на концах строк, передаваемых системных функциям в качестве основного аргумента (обычно это «cmd.exe» или «/bin/sh»).

    Для изгнания нулей из операндов машинных инструкций следует прибегнуть к адресной арифметике. Так, например, MOV EAX,01h (B8 00 00 00 01) эквивалентно XOR EAX,EAX/INC EAX (33 C0 40). Последняя записи, кстати, даже короче. Текстовые строки (вместе с завершающим нулем в конце) так же могут быть сформированы непосредственно на вершине стека, например:

    Как вариант, можно воспользоваться командой XOR EAX,EAX/MOV [XXX], EAX, вставляющей завершающий нуль в позицию XXX, где XXX адрес конца текстовой строки, вычисленный тем или иным способом (см. «в поисках самого себя«).

    Более радикальным средством предотвращения появления нулей является шифровка shell-кода, в подавляющем большинстве случаев сводящаяся к тривиальному XOR. Основную трудность представляет поиск подходящего ключа шифрования – ни один шифруемый байт не должен обращаться в символ нуля. Поскольку, aXORa == 0, для шифрования подойдет любой байтовый ключ, не совпадающий ни с одним байтом shell-кода. Если же в shell-коде присутствует полный набор всех возможных значений от 00h до FFh, следует увеличить длину ключа до слова и двойного слова, выбирая ее так, чтобы ни какой байт накладываемой гаммы не совпадал ни с одним шифруемым байтом. А как построить такую гамму (метод перебора не предлагать)? Да очень просто – подсчитываем частоту каждого из символов shell-кода, отбираем 4 символа, которые встречаются реже всего, выписываем их смещения относительно начала shell-кода в столбик и вычисляем остаток от деления на 4. Вновь записываем полученные значения в столбик, отбирая те, которые в нем не встречаются – это и будут позиции данного байта в ключе. Непонятно? Не волнуйтесь, сейчас все это разберем на конкретном примере.

    Допустим, в нашем shell-кода наиболее «низкочастотными» оказались символы 69h, ABh, CCh, DDh встречающиеся в следующих позициях:

    После вычисления остатка от деления на 4 над каждым из смещений, мы получаем следующий ряд значений:

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

    Теперь из полученных смещений можно собрать гамму, комбинируя их таким образом, чтобы каждый символ встречался в гамме лишь однажды. Смотрите, символ DDh может встречаться только в позиции 00h, символ CCh – только в позиции 03h, а два остальных символа – в любой из оставшихся позиций. То есть это будет либо DDh ABh 69h ССh, либо DD 69h ABh 69h. Если же гамму собрать не удается – необходимо увеличить ее длину. Разумеется, выполнять все расчеты вручную совершенно необязательно и эту работу можно переложить на компьютер.

    Естественно, перед передачей управления на зашифрованный код он должен быть в обязательном порядке расшифрован. Эта задача возлагается на расшифровщик, к которому предъявляются следующие требования: он должен быть а) по возможности компактным, б) позиционно независимым (т. е. полностью перемещаемым) и в) не содержать в себе символом нуля. В частности, червь Love San поступает так:

    вчера были большие, но по пять…
    или размер тоже имеет значение!

    По статистике габариты подавляющего большинства переполняющихся буферов составляет 8 байт. Значительно реже переполняются буфера, вмещающие в себя от 16 до 128 (512) байт, а буферов больших размеров в живой природе практически не встречаются.

    Закладываясь на худший из возможных вариантов (а в боевой обстановке атакующим приходится действовать именно так!), учитесь выживать даже в жесточайших условиях окружающей среды с минимум пиши, воды и кислорода. В крошечный объем переполняющегося буфера можно вместить очень многое, если подходить ко всякому делу творчески и думать головой.

    Первое (и самое простое), что пришло нашим хакерским предкам в голову – это разбить атакующую программу на две неравные части – компактную голову и протяжный хвост. Голова обеспечивает следующие функции: переполнение буфера, захват управления и загрузку хвоста. Голова может нести двоичный код, но может обходиться и без него, осуществляя всю диверсионную деятельность руками уязвимой программы. Действительно, многие программы содержат большое количество служебных функций, дающих полный контроль над системой или, на худой конец, позволяют вывести себя из строя и пойти в управляемый разнос. Искажение одной или нескольких критических ячеек программы, ведет к ее немедленному обрушению и количество искаженных ячеек начинает расти как снежный ком. Через длинную или короткую цепочку причинно-следственных последствий в ключевые ячейки программы попадают значения, необходимые злоумышленнику. Причудливый узор мусорных байт внезапно складывается в законченную комбинацию, замок глухо щелкает и дверцы сейфа медленно раскрываются. Это похоже на шахматную головоломку с постановкой мата в N ходов, причем состояние большинства полей неизвестно, поэтому сложность задачи быстро растет с увеличением глубины N.

    Конкретные примеры головоломок привести сложно, т. к. даже простейшие из них занимают несколько страниц убористого текста (в противном же случае листинги выглядят слишком искусственно, а решение лежит буквально на поверхности). Интересующиеся могут обратиться к коду червя Slapper, до сих пор остающимся непревзойденным эквилибристом по глубине атаки и детально проанализированным специалистами копании Symantec, отчет которых можно найти на их же сайте (см. «An Analysis of the Slapper Worm Exploit»).

    Впрочем, атаки подобного типа скорее относятся к экзотике интеллектуальных развлечений, чем к практическим приемам вторжения в систему и потому чрезвычайно мало распространены. В плане возвращения к средствам традиционной «мануальной терапии», отметим, что если размер переполняющегося буфера равен 8 байтам, отсюда еще не следует, что и длина shell-кода должна быть равна тем же 8 байтам. Ведь это же переполняющийся буфер! Но не стоит бросаться и в другую крайность, надеяться, что предельно допустимая длина shell-кода окажется практически неограниченной. Подавляющее большинство уязвимых приложений содержат несколько уровней проверок корректности пользовательского ввода, которые будучи даже не совсем правильно реализованными, все-таки налагают определенные, под час весьма жесткие, ограничения на атаку.

    Если в куцый объем переполняющегося буфера вместить загрузчик никак не удается, атакующий переходит к плану «B», заключающемуся в поиске альтернативных способов передачи shell-кода. Допустим, одно из полей пользовательского пакета данных допускает переполнение, приводящее к захвату управления, но его размер катастрофически мал. Но ведь остальные поля тоже содержаться в оперативной памяти! Так почему бы не использовать их для передачи shell-кода? Переполняющийся буфер, воздействуя на систему тем или иным образом, должен передать управление не на свое начало, а на первый байт shell-кода, если конечно, атакующий знает, относительный или абсолютный адрес последнего в памяти. Поскольку, простейший способ передачи управления на автоматические буфера сводится к инструкции JMP ESP, то наиболее выгодно внедрять shell-код в те буфера, которые расположены в непосредственной близости от вершины стека, в противном случае ситуация рискует самопроизвольно выйти из под контроля и для создания надежно работающего shell-кода атакующему придется попотеть. Собственно говоря, shell-код может находится в самых неожиданных местах, например, в хвосте последнего TCP-пакета (в подавляющем большинстве случаев он попадает в адресное пространство уязвимого процесса, причем зачастую располагается по более или менее предсказуемым адресам).

    В более сложных случаях shell-код может быть передан отдельным сеансом, – злоумышленник создает несколько подключений к серверу, по одному передается shell-код (без переполнения, но в тех полях, размер которых достаточен для его вмещения), а другому –запрос, вызывающий переполнение и передающий управление на shell-код. Дело в том, что в многопоточных приложениях локальные стеки всех потоков располагаются в едином адресном пространстве процесса и их адреса назначаются не хаотичным, а строго упорядоченным образом. При условии, что между двумя последними подключениями, установленными злоумышленником, к серверу не подключился кто-то еще, «трас-поточное» определение адресов представляет собой хоть и сложную, но вполне разрешимую проблему.

    в поисках самого себя

    Предположим, что shell-код наделен сознанием (хотя это и не так). Что бы мы ощутили оказавшись на его месте? Представьте себе, что вы диверсант-десантник которого выбрасывают куда-то в пустоту. Вас окружает враждебная территория и еще темнота. Где вы? В каком месте приземлились? Рекогносцировка на местности (лат. recognoscere [рассматривать] – разведка с целью получения сведений о расположении противника, его огневых средствах, особенностях местности, где предполагаются боевые действия, и т. п. проводимая командирами или офицерами штаба перед началом боевых действий) и будет вашей первой задачей (а если вас занесет в болото, то и последней тоже).


    Соответственно, первой задачей shell-кода является определение своего местоположения в памяти или более строго говоря, текущего значения регистра указателя команд (в, частности, в x86-процессорах это регистр EIP).

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

    Использование абсолютной адресации (или, говоря другими словами, жесткой привязки к конкретным адресам, вроде MOV EAX, [406090h]) ставит shell-код в зависимость от окружающей среды и приводит к многочисленным обрушениям уязвимых приложений, в которых буфер оказался не там, где ожидалось. «Из чего только делают современных хакеров, что они даже переполнить буфер, не угробив при этом систему, оказываются не в состоянии?» вздыхает прошлое поколение. Чтобы этого не происходило, shell-код должен быть полностью перемещаемым – т. е. уметь работать в любых, заранее ему неизвестных адресах.

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

    Семейство x86-процессоров с относительной адресаций категорически не в ладах и разработка shell-кода для них – это отличная гимнастика для ума и огромное поле для всевозможных извращений. Всего имеется две относительных команды (CALL и JMP/Jx с опкодами E8h и Ebh,E9h/7xh,0F 8xh соответственно) и обе – команды управления. Непосредственное использование регистра EIP в адресных выражениях запрещено.

    Использование относительных CALL’ов в 32-разрядном режиме имеет свои трудности. Аргумент команды задается знаковым 4-байтовым целым, отсчитываемым от начала следующей команды и, при вызове нижележащих подпрограмм, в старших разрядах содержащих одни нули. А, поскольку, в строковых буферах символ нуля может встретиться лишь однажды, такой shell-код просто не сможет работать. Если же заменить нули на что-то другое, можно совершить очччень далекий переход, далеко выходящий за пределы выделенного блока памяти.

    Илон Маск рекомендует:  Создаем учетную запись в facebook. Инструкция в картинках

    Уж лучше прыгать назад – в область младших адресов, тогда нули волшебным образом превратятся в символы с кодом FFh (которые, кстати говоря, так же относятся к категории «трудных» символов, которые соглашаются проглотить далеко не все уязвимые программы). Применив военную хитрость и засадив в инструкцию префикс 66h, мы не только сократим длину машинной команды на один байт (что в ряде случаев весьма актуально), но и оторвем два старших байта операнда (те, которые были с нулевыми символами).

    В машинном виде все это выглядит приблизительно так:

    Сказанное справедливо и по отношению к команде JMP, за тем лишь исключением, что команды условного перехода (равно как и команда JMP SHORT) размешают свой адрес перехода в одном-единственном байте, что не только усиливает компактность кода, но и избавляет нас от проблемы «трудных» символов.

    Если же необходимо совершить переход по абсолютному адресу (например, вызвать некоторую системную функцию или функцию уязвимой программы) можно воспользоваться конструкцией CALL регистр/JMP регистр, предварительно загрузив регистр командой MOV регистр, непосредственный операнд (от нулевых символов можно избавиться с помощью команд адресной арифметики) или командой CALL непосредственный операнд с опкодом FF /2, 9A или FF /3 для ближнего, дальнего и перехода по операнду в памяти соответственно.

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

    Стек можно использовать и для подготовки строковых/числовых аргументов системных функций, формируя их командой PUSH и передавая через относительный указатель ESP + X, где X может быть как числом, так и регистром. Аналогичным образом осуществляется и подготовка самомодифицирующегося кода – мы «пушим» код в стек и модифицируем его, отталкиваясь от значения регистра ESP.

    Любители же «классической миссионерской» могут пойти другим путем, определяя текущую позицию EIP посредством конструкции CALL $ + 5/RET, правда в лоб такую последовательность машинных команд в строковой буфер не передать, т. к. 32-раязрдярый аргумент команды CALL содержат несколько символов нуля. В простейшем случае они изгоняются «заклинаниям» 66 E8 FF FF C0, которое эквивалентно инструкциям CALL $‑3/INC EAX наложенным друг на друга (естественно, это может быть не только EAX и не только INC). Затем лишь остается вытолкнуть содержимое верхушки стека в любой регистр общего назначения, например, EBP или EBX. К сожалению, без использования стека здесь не обойтись и предлагаемый метод требует, чтобы указатель вершины стека смотрел на выделенный регион памяти, доступной на запись. Для перестраховки (если переполняющийся буфер действительно срывает стек на хрен) регистр ESP рекомендуется инициализировать самостоятельно. Это действительно очень просто сделать, ведь многие из регистровых переменных уязвимой программы содержат предсказуемые значения, точнее – используются предсказуемым образом. Так, в Си++ программах ECX наверняка содержит указатель this, а this это не только ценный мех, но и как минимум 4 байта доступной памяти!

    В порядке дальнейшего развития этой идеи отметим, что не стоит, право же, игнорировать значения регистров, доставшихся shell-коду в момент начала его выполнения. Многие из них указывают на полезные структуры данных и выделенные регионы памяти, которые мы гарантированно можем использовать, не рискуя нарваться на исключение и прочие неожиданные неприятности. Некоторые регистровые переменные чувствительны к версии уязвимого приложения, некоторые – к версии компилятора и ключам компиляции, так что «гарантированность» эта очень и очень относительна, впрочем, как и все сущее на земле.

    техника вызова системных функций

    Возможность вызова системных функций, строго говоря, не является обязательным условием успешности атаки, поскольку все необходимое для атаки жертва (уязвимая программа) уже содержит внутри себя, в том числе и вызовы системных функций вместе с высокоуровневой оберткой прикладных библиотек вокруг них. Дизассемблировав исследуемое приложение и определив целевые адреса интересующих нас функций, мы можем сделать CALL целевой адрес или PUSH адрес возврата/JMP относительный целевой адрес или MOV регистр, абсолютный целевой адрес/PUSH адрес возврата/JMP регистр.

    Замечательно, если уязвимая программа импортирует пару функций LoadLibrary/GetProcAddress, – тогда shell-код сможет загрузить любую динамическую библиотеку и обратиться к любой из ее функций. А если функции GetProcAddress в таблице импорта нет? Тогда – атакующий будет вынужден самостоятельно определять адреса интересующих его функций, отталкиваясь от базового адреса загрузки, возращенным LoadLibrary и действуя либо путем «ручного» разбора PE-файла, либо отождествляя функции по их сигнатурам. Первое сложно, второе – ненадежно. Закладывается на фиксированные адреса системных функций категорически недопустимо, поскольку они варьируются от одной версии операционной системы к другой.

    Хорошо, а как быть когда функция LoadLibrary в таблице импорта конкретно отсутствует и одной или нескольких системных функций, жизненно необходимых shell-коду для распространения, там тоже нет? В UNIX-системах можно (и нужно!) использовать прямой вызов функций ядра, реализуемый либо посредством прерывания по вектору 80h (LINUX, Free BSD, параметры передаются через регистры), либо через дальний CALL по адресу 0007h:00000000h (System V, параметры передаются через стек), при этом номера системных вызовов содержатся в файле /usr/include/sys/syscall.h, так же смотри врезку. Еще можно вспомнить машинные команды syscall/sysenter, которые, как и следует из их названия, осуществляют прямые системные вызовы вместе с передачей параметров. В Windows NT и производных от нее системах дела обстоят намного сложнее. Взаимодействие с ядром реализуется посредством прерывания INT 2Eh, неофициально называемого nativeAPIinterface («родной» API интерфейс). Кое-какая информация на этот счет содержится в легендарном InterruptList’e Ральфа Брауна и «Недокументированных возможностях Windows NT» Коберниченко, но мало, очень мало. Это чрезвычайно скудно документированный интерфейс и единственным источником данных остаются дизассемблерные листинги KERNEL32.DLL и NTDLL.DLL. Работа cnative API требует высокого профессионализма и глубокого знания архитектуры операционной системы, да и как-то громоздко все получается, – ядро NT оперирует с небольшим числом довольно примитивных (или, если угодно, – низкоуровневых) функций. К непосредственному употреблению они непригодны и, как и всякий полуфабрикат, должны быть соответствующим образом приготовлены. Например, функция LoadLibrary «распадается» по меньшей мере на два системных вызова – NtCreateFile (EAX == 17h) открывает файл, NtCreateSection (EAX == 2Bh) проецирует файл в память (т. е. работает как CreateFileMapping), после чего NtClose (EAX == 0Fh) со спокойной совестью закрывает дескриптор. Что же касается функции GetProcAddress, то она целиком реализована в NTDLL.DLL и в ядре даже не ночевала (впрочем, при наличии спецификации PE-формата – она входит в Platform SDK и MSDN – таблицу экспорта можно проанализировать и в «ручную»).

    С другой стороны, обращаться к ядру для выбора «эмулятора» LoadLibrary совершенно необязательно, поскольку библиотеки NTDLL.DLL и KERNEL32.DLL всегда присутствуют в адресном пространстве любого процесса и если мы сможем определить адрес их загрузки, мы сорвем банк. Автору известно два способа решения этой задачи – через системный обработчик структурных исключений и через PEB. Первый – самоочевиден, но громоздок и неэлегантен, а второй элегантен, но ненадежен. «PEB только на моей памяти менялась три раза» (с) Юрий Харон. Однако, последнее обстоятельство ничуть не помешало червю Love San разбросать себя по миллионам машин.

    Если во время выполнения приложения возникает исключительная ситуация (деление на ноль или обращение к несуществующей странице памяти, например) и само приложение никак ее не обрабатывает, то управление получает системный обработчик, реализованный внутри KERNEL32.DLL и в W2K SP3 расположенный по адресу 77EA1856h. В других операционных системах этот адрес будет иным, поэтому грамотно спроектированный shell-код должен автоматически определять адрес обработчика на лету. Вызывать исключение и трассировать код (как это приходилось делать во времена старушки MS-DOS) теперь совершенно необязательно. Лучше обратиться к цепочке структурных обработчиков, упакованных в структуру EXCEPTION_REGISTRATION, первое двойное слово которых содержит указатель на следующий обработчик (или FFFFFFFFh, если никаких обработчиков больше нет), а второе – адрес данного обработка

    Переполнение буфера

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

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

    В материале «Википедии» (http://ru.wikipedia.org) дано следующее определение данной уязвимости: «Переполнение буфера – это явление, возникающее, когда компьютерная программа записывает данные за пределами выделенного в памяти буфера».

    В «Новом словаре хакера» Эрика С. Рэймонда сказано, что «переполнение буфера – это то, что с неизбежностью происходит при попытке засунуть в буфер больше, чем тот может переварить».

    Представьте следующую ситуацию. Функция изменения пароля может воспринять пароль длиной не более 256 символов. Чаще всего никто не пользуется паролями длиннее 8–10 символов, поэтому разработчики не предусмотрели проверку строки ввода данных. При попытке ввести более 256 символов, если за данными находился адрес возврата функции, он будет перезаписан и в лучшем случае выполнение программы завершится с ошибкой. Хакеру, обнаружившему такой уязвимый участок, остается подставить в качестве адреса возврата правильный адрес, что переведет управление в любую точку программы на его выбор. В результате может быть выполнен любой произвольный код, который хакер поместил в указанную область памяти, с привилегиями, с которыми выполняется текущая программа.

    Подобные ошибки в программном обеспечении находят чуть ли не ежедневно, но не всегда и не сразу устраняют. Для примера можно просмотреть статистику на специализированных сайтах. Согласно данным Secunia (http://secunia.com) в Microsoft Windows XP Professional не устранено 30 из 201 уязвимостей, обнаруженных с 2003 по начало 2008 года, хотя имеющих статус highly critical (предельно опасный), которые позволяют удаленно выполнить код (то есть фактически получить доступ к системе), в этом списке уже нет. В среднем обнаруживается три-четыре уязвимости в месяц.

    В Internet Explorer 7 не устранено 7 из 21 найденной уязвимости, и некоторые из них имеют статус highly critical. В среднем в месяц обнаруживается одна-две уязвимости. Если учесть все уязвимости, при которых можно удаленно выполнить код в системе, можно сделать вывод, что с этим браузером вообще опасно выходить в Интернет. Internet Explorer позволяет выполнение кода при обработке HTML Help ActiveX, файлов HTA, SWF, ZIP, BMP и JPEG, архивов FTP, Cookies, тега IFRAME и всплывающих окон, то есть для проникновения в систему троянца достаточно зайти на сайт и просмотреть/сохранить рисунки или архив ZIP.

    Внимание!

    Найденные уязвимости далеко не всегда устраняются сразу, часто они присутствуют годами.

    В операционной системе Windows код Internet Explorer используют и многие другие приложения (почтовый клиент Outlook Express, Проигрыватель Windows Media и др.), отчего увеличивается вероятность поражения. Например, существует JPEG-эксплоит, который использует ошибку в системной библиотеке, возникающую при обработке графических JPEG-файлов. Эта ошибка позволяет заразить компьютер через любую программу – Outlook Еxpress или любой другой почтовый клиент, показывающий JPEG-картинки.

    Следует также обратить внимание на возможность вывода в адресной строке Internet Explorer адреса, не соответствующего адресу загруженного сайта (такой вид атаки называется URL-спуфинг), и отображение всплывающих окон, которые, как думает пользователь, принадлежат просматриваемой в браузере странице, а также модификация заголовка окна или информации в строке состояния.

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

    Давайте посмотрим, что на сегодня можно сказать о самом известном из бесплатных браузеров – Mozilla Firefox 2. В нем обнаружено 19 уязвимостей, из которых не устранено четыре. Самая опасная из них имеет статус less critical (ниже критического). Использовав эти уязвимости, получить полное управление компьютером невозможно, злоумышленник может лишь раскрыть некоторую системную информацию, поэтому следует признать, что положение этого браузера лучше, чем Internet Explorer.

    Таким образом, оптимальным решением будет использование альтернативных приложений. Вместо Internet Explorer для серфинга можно применять, например, Mozilla Firefox, вместо Outlook Еxpress – The Bat! и т. д. У этих программ ситуация с безопасностью лучше.

    Следует также периодически устанавливать обновления и использовать новые версии программ, в которых устранены старые ошибки (возможные новые ошибки – это уже другой вопрос). Правда, размер обновлений подчас до стигает нескольких десятков мегабайт и кроме Internet Explorer и Windows необходимо обновлять другие продукты, следовательно, трафик может быть накладным для бюджета. В таком случае следует ограничиться хотя бы обновлениями, устраняющими критические ошибки.

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

    На борьбу с данной уязвимостью направлены такие утилиты, как, например, Stack-Guard. Первоначально она была разработана для Unix-систем, но в настоящее время ее аналог используется в Microsoft Visual Studio.NET и ProPolice компании IBM. В продуктах компании Microsoft, Windows XP SP2 и Server 2003 SP1 используется технология DEP (Data Execution Protection – защита от выполнения данных), которая делает секцию данных и стек неисполняемыми, что должно предотвращать подобный тип атак.

    В некоторых процессорах Intel и AMD имеется специальный бит: в Intel – XD (eXecute Disable – запрет выполнения), в AMD – NX (No eXecute – нет выполнения), позволяющий аппаратно реализовать поддержку DEP.

    При отсутствии поддержки на компьютере неисполняемых страниц на аппаратном уровне используется Software-enforced DEP (принудительный программный DEP). Программная защита DEP встраивается во время компиляции и поэтому работает только для пересобранных с поддержкой DEP системных библиотек и приложений.

    В этих средствах можно обнаружить и недостатки. При использовании технологии DEP встал вопрос совместимости. Некоторым приложениям требуется исполняемый стек (а таких много: эмуляторы, компиляторы, защитные механизмы и пр.), поэтому по умолчанию защита включена только для системных процессов. Появление новой технологии подстегнуло хакеров, и практически одновременно были представлены методы, позволяющие обойти защиту. Атаковать стало сложнее, но все равно возможно.

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

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

    Переполнение буфера

    Переполнение буфера (Вuffer Overflow) — явление, возникающее, когда компьютерная программа записывает данные за пределами выделенного в памяти буфера.

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

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

    Переполнение буфера может вызывать аварийное завершение или зависание программы, ведущее к отказу обслуживания (denial of service, DoS). Отдельные виды переполнений, например переполнение в стековом кадре, позволяют злоумышленнику загрузить и выполнить произвольный машинный код от имени программы и с правами учетной записи, от которой она выполняется.

    Иногда, переполнение буфера намеренно используется системными программами для обхода ограничений в существующих программных или программно-аппаратных средствах. Например, операционная система iS-DOS (для компьютеров ZX Spectrum) использует возможность переполнения буфера встроенной TR-DOS для запуска своего загрузчика в машинных кодах (что штатными средствами в TR-DOS сделать невозможно).

    Безопасность Править

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

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


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

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

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

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

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

    Описание Править

    Рассмотрим более подробно случай переполнения буфера, расположенного в области стека. Это удобнее всего сделать с помощью примера программы на языке Си. Пример ориентирован на архитектуру x86.

    Когда динамический буфер, представляющий собой автоматический массив, выделяется в функции, он создаётся на стеке во время вызова этой функции. В архитектуре x86 стек растёт от бо́льших адресов к меньшим (или справа налево, в приведённых ниже диаграммах), то есть новые данные помещаются перед теми, которые уже находятся в стеке. Здесь, (DATA) (DATA) (…) представляет существующий стек, и (NEWDATA) — это некоторое новое значение, которое ЦП поместил в стек:

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

    Когда выделятся динамический буфер, стек растёт влево на размер буфера. Так, если функция начинается с объявления char a[10] , результатом будет:

    В конце подпрограммы память, занятая буфером, освобождается, и вызывается операция RET . Она извлекает адрес возврата из стека и выполняет переход по этому адресу, возвращая управление туда, откуда была вызвана подпрограмма.

    Предположим, что 10-байтный буфер предназначен для того, чтобы содержать данные, предоставляемые пользователем (например — пароль). Если программа не проверяет количество символов, которые были введены пользователем, и записывает 14 байт в буфер, эти лишние данные будут помещены поверх адреса возврата. Таким образом, это изменит адрес, по которому будет передано управление, когда завершится подпрограмма, и с которого программа продолжит исполнение после этого.

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

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

    Пример Править

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

    Программу можно опробовать с несколькими разными строками. Строки размером в 9 или меньше символов не будут вызывать переполнение буфера. Строки в 10 и более символов будут вызывать переполнение, хотя это может и не приводить к ошибке сегментации.

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

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

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

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

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

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

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

    Существуют две системы: StackGuard и Stack-Smashing Protector (старое название — ProPolice), обе являются расширениями компилятора gcc. Начиная с gcc-4.1-stage2, SSP был интегрирован в основной дистрибутив компилятора. Gentoo Linux и OpenBSD включают SSP в состав распространяемого с ними gcc.

    Размещение адреса возврата в стеке данных облегчает задачу осуществления переполнения буфера, которое ведёт к выполнению произвольного кода. Теоретически, в 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++, потому что они не скрывают детали низкоуровневого представления буферов как контейнеров для типов данных. Таким образом, чтобы избежать переполнения буфера, нужно обеспечивать высокий уровень контроля за созданием и изменениями программного кода, осуществляющего управление буферами. Использование библиотек абстрактных типов данных, которые производят централизованное автоматическое управление буферами и включают в себя проверку на переполнение — один из инженерных подходов к предотвращению переполнения буфера.

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

    Переполнение буфера

    • Переполнение буфера (англ. Buffer Overflow) — явление, возникающее, когда компьютерная программа записывает данные за пределами выделенного в памяти буфера.

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

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

    Переполнение буфера может вызывать аварийное завершение или зависание программы, ведущее к отказу обслуживания (denial of service, DoS). Отдельные виды переполнений, например переполнение в стековом кадре, позволяют злоумышленнику загрузить и выполнить произвольный машинный код от имени программы и с правами учетной записи, от которой она выполняется.

    Известны примеры, когда переполнение буфера намеренно используется системными программами для обхода ограничений в существующих программных или программно-аппаратных средствах. Например, операционная система iS-DOS (для компьютеров ZX Spectrum) использовала возможность переполнения буфера встроенной TR-DOS для запуска своего загрузчика в машинных кодах (что штатными средствами в TR-DOS сделать невозможно).

    Связанные понятия

    В информатике бу́фер (англ. buffer), мн. ч. бу́феры — это область памяти, используемая для временного хранения данных при вводе или выводе. Обмен данными (ввод и вывод) может происходить как с внешними устройствами, так и с процессами в пределах компьютера. Буферы могут быть реализованы в аппаратном или программном обеспечении, но подавляющее большинство буферов реализуется в программном обеспечении. Буферы используются, когда существует разница между скоростью получения данных и скоростью их обработки.

    Упоминания в литературе

    Связанные понятия (продолжение)

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

    Сопрограммы (англ. coroutines) — методика связи программных модулей друг с другом по принципу кооперативной многозадачности: модуль приостанавливается в определённой точке, сохраняя полное состояние (включая стек вызовов и счётчик команд), и передаёт управление другому. Тот, в свою очередь, выполняет задачу и передаёт управление обратно, сохраняя свои стек и счётчик.

    Операционные системы используют менеджеры блокировок (англ.) для организации и координации доступа к ресурсам. Распределенный менеджер блокировок (англ. Distributed lock manager, DLM, ) работает на каждой машине в кластере, с идентичной копией базы данных блокировок кластера. Таким образом, DLM является пакетом программного обеспечения, который позволяет компьютерам в кластере координировать доступ к совместно используемым ресурсам .

    Понравилась статья? Поделиться с друзьями:
    Кодинг, CSS и SQL