![]() |
Что такое переполнение буфера?
Article: Переполнение - что это такое?
Author: Great Date: 07.11.2006 Note: В статье предполагается знание читателем языков программирования C, Assembler Статья является ознакомительным вводным материалом для знакомства читателя с темой информационной безопасности. Автор не несет никакой ответственности за прямой или косвенный ущерб, каким-либо образом нанесенный использованием или не использованием данного материала. Вся ответственность за незаконное использование материалов этой статьи ложится только на вас. I. Переполнение буфера. Что такое? Рассмотрим небольшой, но очень эффектный пример. Код:
void get_user_name()II. Что полезного оно нам дает? Разберемся теперь, что же полезного нам дает переполнение локального буфера. Мы можем перезаписать адрес возврата и устремить процессор на наш (возможно, злобный :)) код. Удаленно это позволит выполнять произвольный код на целевой системе, локально, если программа запущена под root'ом, это позволит нам получить привилегии администратора системы. Весьма перспективно, не правда ли? III. Практика Перейдем же, наконец, к практике. Для начала, нам нужно определить, какие именно байты (по порядку от начала строки) затирают адрес возврата. Делается это элементарно таким образом - вводится строка вида AAAAAAAAAA0123456789. Очевидно, что адрес затрут какие-то 4 байта от 0 до 9 с этой строке. Выясним же, какие. http://gr8.cih.ms/data/overflow1.png Операционная система заботливо сообщает нам, что не может обратиться по адресу 0x39383736. Совершенно несложно проверить, что это коды символов '9', '8', '7' и '6', то есть эти байты затерли адрес возврата. Прекрасно. Теперь нам нужно узнать месторасположение в памяти наших данных. Программа всегда загружается в свое виртуальное адресное пространство по одному и тому же виртуальному адресу, и стек выделяется тоже по фиксированному виртуальному адресу в адресном пространстве нашей программы. Это значит, что адрес строки в памяти с каждым запуском нашей программы меняться не будет. А ведь это здорово, мы можем записать адрес возврата, например, адресом следующих после 6789 байт, где и разместим наш код. Итак, нам нужно узнать адрес данных. Что может быть проще? Загружаем программу в отладчик, вводим строку программе AAAAAAAAAA0123456789zzcv, ждем исключения, открываем дамп стека в отладчике и ищем строку "zzcv" в стеке - начиная с этого адреса мы расположим в дальнейшем наш код. Voil`a! Смотрим в отладчик: http://gr8.cih.ms/data/overflow2.png Смотрим, что строка 'zzcv' найдена по адресу 0x0013ff34. Это не первое вхождение строки в стеке, но отладчик снизу показывает еще содержимое стека с адресами, где тоже виден адрес 0x0013ff34. Так что можно утверждать, что наш код разместится именно там. Попробуем написать вызов MessageBox'а. Нам потребуется компилятор FASMW для того, чтобы собрать код эксплоита. Чуток забегу вперед и скажу, что наш код будет таким: Код:
org 0x0013ff34; base addressС помощью ORG мы задаем место в памяти, с которого будет располагаться наш код во время выполнения. В конце мы располагаем константу KERNEL32_BASE, которая равна базовому адресу загрузки библиотеки kernel32.dll, подгружаемой к каждому процессу, в памяти. Потом расположены адреса некоторых функций в ней, нам потребуются LoadLibraryA и GetProcAddress для вызова MessageBox из user32.dll и ExitProcess для завершения программы. Скажу сразу, этот код не портабелен. Под другой версией ОС Windows, даже в другом сервиспаке или build'е адреса функций неизбежно будут другими. Но это лишь демонстрация, в боевых условиях адрес ядра берется из сегмента FS, а поиск функций производится напрямую в коде ядра. Под *nix-like системами вообще ничего такого производить не нужно, все системные вызовы реализуются одним прерыванием, адрес обработчика которого задается операционной системой в таблице IDT, следовательно, нам вообще не нужно знать месторасположение ядра в памяти. Код PoC эксплоита под linux, выводящего сообщение на консоль,будет гораздо проще. Ну, это лирика. Двинемся дальше. Что же происходит в нашем коде? Подгружается к текущему процессу user32.dll, если она еще не подгружена и в ней ищется функция MessageBoxA для вывода диалогового окошка с сообщением о том, что эксплоит сработал. Итак, пробуем FASMW: Ctrl-F9, скомпилировалось. Берем утилиту мою bin2c, которая переводит бинарник в вид, понятный компилятору си (вида \x00\x01\x02\0x03): \x68\x67\xff\x13\x00\xff\x15\xac\xff\x13\x00\x68\x 72\xff\x13\x00\x50\xff\x15 \xa8\xff\x13\x00\x68\x00\x00\x00\x00\x68\x00\x00\x 00\x00\x68\x86\xff\x13 \x00\x68\x00\x00\x00\x00\xff\xd0\xff\x15\xa4\xff\x 13\x00\x55\x53\x45\x52 \x33\x32\x2e\x44\x4c\x4c\x00\x4d\x65\x73\x73\x61\x 67\x65\x42\x6f\x78 \x41\x00\x45\x78\x70\x6c\x6f\x69\x74\x00\x50\x72\x 6f\x6f\x66\x2d\x6f \x66\x2d\x43\x6f\x6e\x63\x65\x70\x74\x20\x65\x78\x 70\x6c\x6f\x69\x74 \x20\x63\x6f\x64\x65\x00\xda\xcd\x81\x7c\xa0\xad\x 80\x7c\x77\x1d\x80\x7c Пишем код эксплоита на Си: Код:
#include <windows.h>Итак, что же мы имеем. Если у нас есть программа, которая не контролирует выход за пределы локального буфера, мы можем перезаписать часть стека и исполнить код в контексте этой программы. Позволю себе закончить на этой радостной ноте, удачного компилирования ;) К статье прилагаются - два скриншота (overflow1.png, overflow2.png), утилита bin2c с исходными текстами для кодирования бинарного файла в вид Си-строки. http://gr8.cih.ms/data/bin2c.rar * * * Статья для новичков в этой сфере (но навыки кодинга предполагаются, иначе ничего объяснить просто невозможно), чтобы они лучше понимали суть происходящего. Ногами сильно не пинать :) |
Не забудьте выключить защиту стека "/GS-", если используете Visual Studio .NET, по умолчанию включено. Поищите там в Properties проекта.
Вообще клёво! З.Ы. А почему \x00 не воспринимается как конец строки и gets() продолжает считывать? |
Цитата:
|
Кстати, кому лень запускать - вот так выглядит работа эксплоита:
http://gr8.cih.ms/data/result.png |
Функция поиска базы KERNEL32.DLL в сегменте FS:
Код:
qGetKernel32Handle proc nearAdded: Проверил, работает ;) |
Цитата:
Цитата:
|
Код:
_TCHAR * __cdecl _getts ( |
Вот функция для поиска апишки по имени
http://cribble.by.ru/data/getprocaddressex.asm Только, неоптимизированная. Она была написана на си, выдрана с помощью IDA Pro и частично переписана для Fasm. В сети можно найти много аналогов, но мне пока еще не удалось заставить их работать ) |
Довольно грамотно и четко, порадовало что активно используется flat assembler но
Цитата:
Цитата:
буфера в _стеке_ не проще ли просто найти \xFF\xE4 (jmp esp) в ntdll и поставить его адрес для eip ? кто бы что не говорил, jmp esp легче найти где-нибудь так чтоб он был почти везде одинаков. темболее если специфическое приложение использует какую-нибудь свою dll Цитата:
ну и веселая очепятка Цитата:
PS http://cribble.by.ru/data/getprocaddressex.asm Цитата:
я тоже так делаю, просто не показываю :D |
Цитата:
Цитата:
Цитата:
Цитата:
|
| Время: 21:49 |