![]() |
Переполнению буферов подвержены все серьёзные приложения – уязвимости с завидной регулярностью обнаруживаются как в продукции Microsoft, так и в софте энтузиастов. Сколько ошибок не выявлено остаётся только гадать, но чтобы ими воспользоваться, нужно проделать длинный путь. По степени накала страстей, поиск переполняющихся буферов напоминает поиск кладов – его практически невозможно автоматизировать. Трясти следует в первую очередь те буфера, на которые вы можете хоть как-то воздействовать (обычно это связанные с клавиатурным вводом буфы).
1. Источник угрозы 2. Переполнение буферов в стеке 3. Переполнение буферов в куче 4. Уязвимость в спецификаторах printf() 5. Выводы 1. Источник угрозы Из имеющих уязвимости переполнения буфера языков, ключевыми являются C и C++, поскольку их рантайм msvcrt.dll работает с памятью более небрежно, чем многие интерпретируемые языки. Но даже если код и написан на безопасном Python, он всё-равно использует написанные на сишке подключаемые модули, а потому по прежнему может быть уязвим. Чтобы понять природу переполнения, нужно разобраться с выделением памяти в программе – в официальных источниках оно числится как «Memory Allocation». Как правило память под буфер можно выделить или в стеке (резервируется во время компиляции), или-же в куче «Heap» уже во время выполнения. Атаки на стек встречаются чаще, т.к. при вызове функции в нём всегда лежит «адрес возврата» обратно к вызывающему, модифицировав который можно передать управление на произвольный участок кода. Поскольку куча не хранит Return-адресов, то здесь гораздо сложнее запустить свой сплойт или шелл – эта память содержит лишь данные программы. Однако куча имеет тенденцию расширяться динамически, и если прогер использовал это свойство вызовом функции Код:
realloc()2. Переполнение буферов в стеке Причина, по которой «BufferOverflow» стало серьёзной проблемой заключается в отсутствии проверки границ во многих функциях управления памятью C и C++. Обычно пользовательский буфер переполняется, когда код зависит от внешних входных данных (например ввод с клавиатуры), или когда он имеет зависимости за пределами прямой видимости стекового фрейма, который отображён на рисунке ниже (архитектура IA32). Рассмотрим запрашивающий имя следующий код на ассемблере FASM: C-подобный: Код:
format pe consoleКод:
CALLКод:
RETПроблема в том, что большинство функций рантайма для хранения временных значений всегда используют локальную память функции именно в стеке, что влечёт за собой катастрофические последствия. С одной строны это удобно, т.к. на выходе из функции компилятор сам очищает лок.память предотвращая её утечку (не нужен постоянный контроль). Но с другой стороны программист не может заранее предугадать, буфер какого именно размера потребуется в стековой памяти, и резервирует разумно-ограниченное её кол-во как на рис.выше (пусть будет) 32 байта. Например если код запрашивает имя юзера, оно может иметь длину 4-байта «Вася», а может принадлежать Лоуренсу Уоткинсу с длинною в 2.253 слов. Здесь уже прогерам приходится садиться на шпагат, и выбирать золотую середину. Как результат, ввод 44-байтной строки в выделенный зелёным лок.буфер приведёт к тому, что мы пробьём дно буфера 0-31, и затерём сначала обе переменные, а затем и адрес-возврата из функции, подсунув вместо него указатель на свой шелл. Единственная проблема здесь в том, как из копируемой в буфер строки сформировать фиктивный адрес-возврата, т.к. клавиатурный ввод поддерживает только ASCII-коды печатных символов, и создать из них HEX-адрес не так просто (ситуацию усугубляет, если в адресе присутствует двоичный нуль). Однако при наличии смекалки и эту проблему можно решить (см.ниже). Чтобы предотвратить модификацию адреса-возврата при переполнении буфера, инженеры Microsoft предлагают нам 2 основных решения – это замена уязвимой функции Код:
gets()Код:
fgets()3. Переполнение буферов в куче Куча «Heap» – это область динамической памяти программы для выделения блоков произвольного размера. Для временных буферов рекомендуется выделять память именно в куче, а не в стеке, т.к. в ней будет отсутствовать уязвимый адрес-возврата. Тогда какой интерес представляет куча для взломщиков? Чтобы ответить на этот вопрос, нужно рассмотреть способ динамического распределения памяти. В основе управления хипом лежит функция Код:
malloc()Код:
calloc()Код:
realloc()Код:
free()Код:
void * malloc ( size ) ;Код:
void * calloc ( size, num ) ;Size = размер блока, Num = их кол-во. Эти аргументы можно переставить местами. Код:
void * realloc ( ptr, size ) ;Ptr = указатель на выделенную ранее память, Size = новый размер блока. Код:
void free ( ptr ) ;Ptr = указатель на блок, который необходимо освободить. Суть в том, что при выделении памяти с помощью Код:
malloc()Код:
realloc()Сами метаданные хранят всего 2 указателя для организации связанного списка – это линк на метаданные предыдущего свободного блока памяти, и линк на метаданные следующего блока. Чтобы разобраться с форматом служебных данных, можно последовательно запросить 2 блока памяти подряд, и посмотреть на результат в отладчике: C-подобный: Код:
mainКод:
calloc()Код:
0x005D0E78Код:
0x005D00C4Код:
0x005D0EA0Значит продолжаем трейс в отладчике и зовём второй раз Код:
calloc()Код:
0x005D0EA0Таким образом, атака на переполнение буфера в куче подразумевает мод именно метаданных. Если вместо указателей на «Backward/Forward Block» мы сможем подсунуть линк на свой шелл, то при сл.вызове Код:
calloc()Код:
realloc()Код:
malloc/calloc()Код:
gets()Код:
memcpy()Реализация переполнения буферов в куче требует тщательного разбора метаданных, а потому этот способ не получил широкого распространения. Единственно возможный вариант защиты от атак подобного рода является ввод с ограничением длины Код:
fgets()Код:
gets()4. Фиктивные спецификаторы функции printf() Команды «peek» и «poke» использовались в Basic для доступа к содержимому ячейки памяти – «peek» читает байт по указанному адресу, а «poke» записывает. Термины устарели, но по привычке используются программистами для обозначения доступа к памяти в целом. Для начала посмотрим на список функций из либы msvcrt.dll (Microsoft VisualC Runtime), которые представляют для нас особый интерес: Известно, что функции Код:
printf() + scanf()Код:
scanf('%s',*buff)Код:
printf('%X',var)Проблема в том, что эти функции страдают классическим недержанием кол-ва переданных им спецификаторов, что является огромной дырой в подсистеме безопасности. Они могут принимать произвольное кол-во спецификаторов, и этот факт позволяет нам навязывать фиктивные, по своему усмотрению. Рассмотрим такой пример, где функция Код:
gets()Код:
sprintf()Код:
printf()C-подобный: Код:
format pe consolehttps://forum.antichat.xyz/attachmen...4fdbb2488d.png Теперь посмотрим на реакцию функции Код:
sprintf()https://forum.antichat.xyz/attachmen...9251a229fe.png Как видим, глупая функция приняла наш ввод за свои спецификаторы, в результате чего прихватила с собой 5 следующих двордов из стека – первые(2) это лок.переменные см.исходник, третий это значение сохранённого на входе в процедуру регистра Код:
EBPКод:
stdcall (0x10)Таким образом, взяв в заложники спецификаторы функции Код:
printf()Ладно, будем считать, что с чтением памяти определились. Но на практике интерес представляет запись, чтобы модифицировать, например, всё тот-же адрес-возврата. Примечательно, что Код:
printf()Код:
%nНу и конечно классический вариант перезаписи ячеек функцией Код:
gets(buff)Код:
scanf('%s',*buff)Если функция внутри процедуры запрашивает ввод текстовой строки, то не прибегая к лишним усилиям мы можем подсунуть ей HEX-значения в формате ASCII-кодов. Когда с комбинацией Код:
AltКод:
00hКод:
FFhРассмотрим это дело на таком примере, где запрашивается строка со-спецификатором(%s), но для демонстрации мы распечатаем её в HEX. C-подобный: Код:
cinvoke printfКод:
0x0014E2F4Код:
0xF4E21400Код:
244 226 20Код:
Althttps://forum.antichat.xyz/attachmen...dc62741817.png 5. Выводы В своей массе, переполнению буферов подвержены только консольные приложения, которые импортируют функции из либы msvcrt.dll (хидер stdio.h). Однако и в оконных приложениях довольно часто встречается WinAPI Код:
wsprintf()Ну и под занавес вот несколько рекомендаций для защиты от переполнений буферов в стеке и куче: 1. Используйте только безопасные функции ввода с ограничением длины типа Код:
fgets()Код:
Read/WriteConsole()2. Используйте «Canaries Word», которые вставляют контрольное слово перед адресом-возврата в стеке, и проверяются перед обращением к нему. 3. Не используйте локальные буферы в стеке, а выделяйте для них память из кучи. 4. Собирайте свои исходники с флагом ASLR (рандомизация адресного пространства), чтобы при каждой загрузке системы менялись адреса базы загрузки образа в память, и соответственно адрес его стека. 5. Сделайте стек неисполняемым установив бит NX (No-eXecute), чтобы плохой парень не вставил шелл-код непосредственно в стек. Для этого нужно включить DEP (Data Execution Protect) в свойствах системы. По умолчанию DEP включён только для системных приложений, так-что нужно взвести галку «..для всех». |
| Время: 13:05 |