На главную | Содержание | Назад | Вперёд
Наши друзья

 

 

Успешная атака на неисполняемые стеки

Итак, мы продемонстрировали, что существует множество способов исполнения кода в стеке. Но что делать, если стек является неисполняемым (nonexecutable stack)?
Существует немало параметров для аппаратных средств и среды исполнения операционной системы, которые определяют вид памяти, предназначенной для хранения кода (т.е. для исполняемых данных). Если стек не подходит для хранения кода, хакер может временно отступить, но в его распоряжении остается множество других вариантов. Для получения контроля над системой вовсе необязательно вве­дение кода, достаточно воспользоваться чем-то менее сложным. Существует огромное количество структур данных и вызовов функций, которые, будучи управляемы хакером, могут использоваться для контроля над системой. Рассмотрим следующий фрагмент кода.
void debug_log ((coonst char *mfcrusted_ixrp]ii]ir_(datta)
{
char *_p = new char[8];
// указатель остается выше _t
char _t[24];
strcpy(_t, untrusted_input_data); // _t перезаписывает _р
memcpy(_p, 4_t[101, 88);
//_t[X0] имеет новый адрес, пщреааииисывжемый с помощь» puts()
_t[10]=0; char _log[255];
sprintf(_log, "%s - %аи,, &_t[0], Ж_р?[[41);
// мы управляем первыми 10 символами _log
fnDebugDispatch ((Jxog) ;
// адрес fnDebugDispatch () заменен на адрес функции system() // которая вызывает командный интерпретатор...
В этом примере выполняется несколько небезопасных операций в буфере с указателем. Мы можем управлять значением _р с помощью переполнения _t. Целью нашей программы атаки является вызов функции fnDebugDispatch (). Для этого вызова в качестве параметра передается буфер, и при этом мы управляем первыми десятью символами этого буфера. Машинный код, который выполняет этот вызов, выглядит следующим образом.
24: fnDebugDispatch(_log);
004010А6 8В F4 mov esi,esp
004010А8 8D 85 К4 Ш № FF lea eaaxJetop-llCh] 004010AE 50 push eax
004010AF FF 15 8C 51 41 00 call dword ptr
Ч> Г imp?.fnDebuqDisoatchg9YAHPADgZ(00415150) ]

В этом коде вызывается адрес функции, хранящийся по адресу 0x00415150. Содержимое памяти выглядит следующим образом.
00415150 F0 В7 23 10 00 00 00 00 00 00 00 00 00 00 00 •.#............
0041515F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...............
0041516Е 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .
Если мы изменим хранящийся здесь адрес, то сможем заставить вызвать другую функцию. Адрес функции, который в данное время сохранен в памяти по адресу 0xl023B7F0 (он записан в обратном порядке байтов в дампе памяти).
Всегда есть много функций, загруженных в пространство памяти, выделенное для программы. Используемой нами функции передается один параметр из буфера. Так случилось, что другой функции system () также передается один параметр из буфера. Что произойдет, если мы заменим указатель нашей функции на указатель функции system () ? Мы получим полный контроль над системным вызовом. В нашем примере функция system () хранится по адресу 0х1022В138. Все, что нужно, — это затереть данные по адресу 0x00415150 адресом 0х1022В138. Таким образом мы получаем в свое распоряжение собственный вызов функции system () с контролируемым нами значением параметра.
Существует и альтернативный вариант, если мы не хотим изменять значение памяти по адресу 0x00415150. Как видим, оригинальный код функции fnDebugDispatch () хранится по адресу 0xl023B7F0. Если мы исследуем код по этому адресу, то получим следующее.
0ILT+15(?fnDebugDispatch@@YAHPAD@Z):
10001014 Е9 97 00 00 00 jmp fnDebugDispatch (ЮООЮЬО)
В программе используется таблица переходов. Если мы изменим команду перехода, мы сможем заставить команду jmp вызывать функцию system (). Текущее значение этой команды используется для перехода к функции f nDebugDispatch (OxlOOOlObO). Мы хотим заменить этот вызов вызовом функции system (Ох 1022В138). Текущий машинный код для операции перехода: е9 97 00 00 00. Если мы изменим машинные команды на е9 IF A1 22 00, то команда jmp будет осуществлять переход к функции system(). В результате запускаемую нами ко­манду можно представить следующим образом. system("del /s с:") ;
В заключение хочется сказать, что переполнение буфера действительно является серьезной проблемой. Для блокирования простейших атак на переполнение буфера потребуется совсем немного усилий. В целом, при атаках на переполнение буфера можно изменять код, значения указателей функций и искажать критически важные структуры данных.
Резюме
Несмотря на широкое обсуждение атак на переполнение буфера и достаточно большой выбор технической информации для атак на различные платформы, остается еще немало вопросов по этой теме, которые должны быть исследованы и освещены в публикациях. В этой главе рассмотрено большое количество методов, которые удобно применять для взлома программного обеспечения. В целом, мы обнаружили, что искажение данных в памяти остается излюбленным методом хакеров. Возможно, переполнения буфера в стеке когда-то и перестанут быть актуальной темой, если программисты прекратят использовать уязвимые вызовы строковых функций из библиотеки libc. Однако средств для абсолютного решения этой проблемы пока не существует.
В этой главе также были рассмотрены и другие популярные, но более сложные методы искажения данных в памяти, наподобие использования одного "лишнего" бита и переполнения буфера в куче. Компьютерная наука уже более 20 лет занимается проблемой корректного управления данными в памяти, но программный код остается по-прежнему уязвимым для этих простых атак. Очень похоже на то, что программисты будут повторять эти ошибки и следующие 20 лет.
Чуть ли не каждый день обнаруживаются новые и неисследованные ранее методы вредоносного использования памяти. Скорее всего, еще очень долго мы будем свидетелями проявления этих проблем во встроенных системах.

 

На главную | Содержание | Назад | Вперёд
 
Яндекс.Метрика