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

 

 

Пример атаки: взломанный компилятор C++ от компании Microsoft

Попробуем на примере прояснить нашу терминологию и связать ее с реальной жизнью. В этом разделе мы рассмотрим уже неоднократно упоминавшуюся (но очень важную) атаку на переполнение буфера. Безусловно, то, насколько опасной будет атака на переполнение буфера, зависит от конкретной ситуации. Случайное переполнение буфера, которое является простой ошибкой на техническом уровне, не представляет серьезного риска. Но в основном переполнения буфера очень опасны. Это настолько важное явление, что мы посвятили переполнению буфера целую гла­ву (см. главу 7, "Переполнение буфера"). В данном случае мы используем реальный пример, чтобы показать, как шаблон атаки может превратиться в реальную про­грамму атаки. При рассказе мы будем приводить фрагменты кода. Наши читатели могут стать на место злоумышленника, скопировать наш код, скомпилировать его и затем провести атаку на него, чтобы проанализировать результаты. Как вы увидите, это довольно забавный пример.
В феврале 2001 года компания Microsoft добавила свойство безопасности к сво­ему компилятору для языка C++, последняя версия которого называлась и Visual C++.NET и Visual C++ version 7 5. Итак, чтобы использовать программу атаки в сво­их целях, нужно найти взломанную версию компилятора.
Новая функция безопасности компилятора была предназначена для автоматиче­ской защиты потенциально уязвимого исходного кода от некоторых типов атак на переполнение буфера. Защита достигается за счет нового свойства, которое позволя­ет разработчикам продолжать использовать потенциально уязвимые строковые функции, например strcpy () (источник многих проблем), и быть под "защитой" от манипуляции со стеком. Это новое свойство во многом основано на изобретении Криспина Кована (Crispin Cowan) под названием StackGuard и используется при создании стандартного машинного кода (а не кода на промежуточном языке .NET). Обратите внимание, что новая возможность предназначена для защиты любой про­граммы, скомпилированной с помощь "защищенного" компилятора. Другими сло­вами, использование новой возможности должно помочь разработчикам создавать более безопасное программное обеспечение. Однако, в своей взломанной форме, но­вая возможность Microsoft приводит к ложному чувству безопасности, поскольку ее легко преодолеть. По всей видимости, компания Microsoft, как и в прошлом, отдает предпочтение все же производительности, а не безопасности.
StackGuard — не самое лучшее средство для защиты от атак на переполнение буфе­ра. На самом деле это средство было создано под сильным давлением со стороны раз­работчиков. Кован просто внес исправления в генератор кода дсс, чтобы не пришлось создавать новый генератор или полностью менять архитектур}' компилятора дсс.
Новая функция защиты от Microsoft обеспечивает возможность вызывать "защищенный обработчик ошибок" при возникновении потенциально опасной си­туации. Тот факт, что атака может быть выявлена на таком раннем этапе, демонст­рирует мощь концепции шаблонов атак. Однако такой способ реализации защищен­ного обработчика ошибок, привел к тому, что сама функция безопасности Microsoft оказалась уязвимой для атак! Злоумышленник может провести специальную атаку на "защищенную" программу, которая непосредственно взломает защитный меха­низм. Безусловно, этот новый тип атаки определяет новый шаблон.
Существует несколько других, не основанных на StackGuard, методов, которыми могут воспользоваться создатели компиляторов для защиты от атак на переполне­ние буфера. Компания Microsoft предпочла слабое решение более сложному. Это просчет на уровне проекта, который приводит к возможности проведения большого количества атак на программный код, скомпилированный с помощью нового компи­лятора. Таким образом, 'компилятор Microsoft можно назвать "разносчиком уязви­мых мест" в программах.
Вместо того чтобы надеяться на функцию запускаемого во время исполнения компилятора для защиты от атак на переполнение буфера, разработчики и архитек­торы должны установить строгие правила создания программного обеспечения, предполагающие в том числе проверки исходного кода. Для обнаружения потенци­альных проблем в исходном коде программ C++ (для защиты от которых и предна­значена рассматриваемая нами функция Microsoft), могут и должны применяться средства статического анализа, например SourceScope от Citigal или программа с от­крытым исходным кодом ITS4. Гораздо лучше заранее полностью устранить эти проблемы в исходном коде, чем пытаться заблокировать их при попытке атак во время выполнения программы6.
Судя по высказываниям Билла Гейтса в январе 2002 года, компания Microsoft де­лает крутой поворот в сторону обеспечения безопасности. Однако у этой компании надолго хватит работы по усовершенствованиям, поскольку даже в функциях обес­печения безопасности присутствуют просчеты на уровне проекта.
Одним из положительных качеств StackGuard и родственного ему средства от Microsoft является эффективность механизмов проверки. Однако существует не­сколько способов для обхода этих механизмов. Атаки, которые провела Cigital для взлома механизмов защиты Microsoft, вовсе не являются новыми и вовсе не требуют каких-либо исключительных усилий. Если бы специалисты Microsoft ознакомились с литературой о недостатках StackGuard, то они смогли бы избежать возможности проведения таких атак.
Технические подробности атаки
Для создания приложений с функцией так называемой "проверки безопасности буфера" разработчики программ на Visual C+ + .Net (Visual C++ 7.0) могут восполь­зоваться параметром /GS компилятора. В 2001 году сотрудниками Microsoft было написано по меньшей мере две статьи о новой возможности, которые затем были удалены из Internet7. Основываясь на этой документации относительно параметра /GS и исследовав двоичные инструкции, генерируемые компилятором при исполь­зовании этого параметра, исследователи Cigital определили, что параметр /GS по су­ти является Win32-реализацией программы StackGuard. Этот вывод был проверен независимыми исследователями компании Immunix.
Благодаря переполнению буфера в стеке хакер получает массу возможностей для перехвата выполнения программы. При использовании широкоизвестного и попу­лярного шаблона атаки в стеке перезаписывается адрес возврата функции данными, которые предоставляет хакер. Управление передается по адресу в эпилоге функции, в котором хакер размещает вредоносный код.
Разработчики StackGuard впервые предложили идею размещения сигнального слова (canary word) перед адресом возврата из функции в прологе функции. В эпи­логе функции выполняется проверка содержимого стека на предмет того, не был ли изменен адрес возврата функции (по сигнальному слову). Позднее этот метод про­верки был усовершенствован с помощью использования функции XOR для сиг­нального слова и адреса возврата, чтобы не дать хакеру возможности обойти значе­ние сигнального слова и переписать адрес возврата из функции. StackGuard можно считать удачным решением для блокирования некоторых атак на переполнение бу­фера путем выявления этих атак во время выполнения программы. В подобном средстве под названием StackShield используется отдельный стек для хранения ад­ресов возврата из функций.
Изменение адреса возврата из функции не является единственным способом для перехвата управления в программе. В статье на сайте журнала Phrack можно найти описание других возможных атак на переполнение буфера, которые позволяют пре­одолеть защиту, организованную с помощью средств, подобных StackGuard и StackShield8. Шаблон атаки можно охарактеризовать следующим образом. Если в стеке "после" уязвимого буфера существуют переменные, содержащие указатели на функции, то сначала хакер организовывает переполнение буфера, которое искажает данные указатели, и далее при вызове функций по этим указателям управление пе­редается подготовленному коду, сохраненному по указанному адресу памяти. Затем предоставленное хакером значение может быть записано по этому адресу. Идеаль­ной областью памяти для хакера будет указатель функции, которая вызывается позже в программе. В статье журнала Phrack обсуждается, как найти такой ука­затель функции в глобальной таблице смещений (Global Offset Table — GOT). Пример реальной атаки для обхода защиты StackGuard был опубликован по ад­ресу http://www.securityfocus.com/archive/1/83769.
Обзор Microsoft-реализации средства StuckGuard
Много технических сведений по реализации параметра /GS в компиляторе от Microsoft можно найти в трех исходных файлах модуля CRT, а именно: seccinit. с, seccook.c и secfail.c. Другие детали можно узнать, исследовав инструкции, которые генерируются компилятором с установленным параметром /GS.
Единственное средство безопасности (сигнальное слово) инициализируется в вы­зове процедуры CRT INIT. Создан новый вызов библиотеки _set_security__error
handler, который может использоваться для установки определенного пользова­телем обработчика. Указатель функции к обработчику пользователя хранится в гло­бальной переменной userhandler. В эпилоге функции генерируемая компилято­ром инструкция переходит к функции security__checkcookie, определенной в
файле seccook.c. Если сигнальное слово было изменено, вызывается функция
security__error__handler, которая определена в файле secfail.c. Функция
security_error_handler сначала проверяет, был ли инсталлирован предостав­ленный пользователем обработчик. Если это так, то вызывается обработчик пользо­вателя, а если нет, то по умолчанию выводится сообщение "Buffer Overrun Detected" ("Обнаружено переполнение буфера") и работа программы завершается.
В этой реализации функции защиты есть несколько проблемных решений. В Wn-dows не предусмотрено ничего подобного глобальной таблице смещений (GOT) с возможностью записи данных, поэтому даже учитывая описанное выше размеще­ние данных в стеке, злоумышленнику непросто найти подходящий для использова­ния указатель функции. Однако из-за доступности переменной user handler ха­керу совсем не нужно выполнять сложный поиск подходящей цели!
Обход функции защиты для компилятора от Microsoft
Рассмотрим следующую небольшую программу.
#include <stdio.h> #include <string.h>
/*
где
request_data — входной параметр,    в  котором  содержится предоставленная
пользователем  зашифрованная   строка, наподобие
"host=dot.net&id=user_i     d&pw=user_password&cookie=da".
user_id - выходной параметр, который используется для копирования декодиро­ванного   значения    'user_id'.
password- выходной параметр, который используется для копирования декодиро­ванного   значения 'password'
*/
void decode(char   *request_data,    char   *user_id,    char Apassword){ char temp_request[64]; char *p_str;
strcpy(terrp_request,   request_data) ;   P_str = strtok(temp_request,   "&") ;
while(p_str    != NULL){
if   (strncmp(p_str,   "id=",   3)   == 0){
strcpy(user_id,   p_str + 3 );

Шаблоны атак
69
else if   (strncmp(p_str,   "pw=",   3)   == 0){ strcpy(password,  p_str + 3);
}
p_str = strtok(NULL,   "&") ;
}
}
/*
Любая комбинация приведет к ошибке.
*/
int  checkj)assword(char  *id,   char "password) { return -1;
}
/*
Мы используем параметр argv[l]   для передачи строки запроса.
*/
int main(int argc,   char ** argv) {
char user_id[32] ; char password[32] ;
user_id[0]  =  '\0 ' ; password[0]  =  '\0' ;
if   ( argc < 2  ) {
printf("Usage:  victim request.\n") ; return 0;
}
decode( argv[l] , user_id, password) ; if   ( check_password(user_id,  password)   > 0 ){ //  невыполняемый участок программы, printf("Welcome!\n"); }
* else{
printf("Invalid password,   user:%s password:%s.\n", user_id, password) ;
}
return 0;
}
Функция decode содержит буфер, размер которого не проверяется, и пара­метры этой функции могут быть перезаписаны с помощью значения параметра temp_request.
Если программа компилируется с использованием параметра /GS, то невозможно организовать переполнение буфера и изменить ход исполнения программы с помо­щью затирания адреса возврата для функции decode. Однако для переполнения буфера можно использовать значение параметра user_id функции decode с целью заставить ее обращаться сначала к значению описанной выше переменной user_handler! Таким образом при вызове функции strcpy (user_id, p_str + 3 ) ; мы можем назначить желаемое значение для переменной user_handler. На­пример, мы можем заставить ее обращаться к области хранения в памяти функции printf ("Welcome ! \n") ;, таким образом, при обнаружении переполнения буфера оно может представляться установленным пользователем обработчиком ошибки и программа будет исполнять printf ('Welcome ! \n") ;. Наша строка для проведе­ния атаки будет выглядеть примерно следующим образом.
id=[aflpec  перехода]&pw=[любое]АААААА....ААА[адрес  обработчика пользователя]

После компиляции такой "защищенной" выполняемой программы определение адреса области для хранения значения переменной user handler осуществляется достаточно просто при наличии минимальных знаний о восстановлении исходного кода. Результат состоит в том, что программа, которая должна быть защищена от атак, определенного типа оказывается уязвимой именно при проведении этих атак.
Решения
Существует несколько альтернативных методов защиты от атак на переполнение буфера. Лучшим решением является переход разработчиков на языки, обеспечиваю­щие безопасность типов, например на Java или С#. Еще одним прекрасным решением являются компиляция программы с динамическими проверками строковых функций во время выполнения программы (хотя следует учесть снижение производительно­сти). Эти решения не всегда удобны относительно задач конкретного проекта.
Кроме того, возможно изменение использующегося метода компиляции с приме­нением параметра /GS. Основная цель предложенных далее исправлений заключа­ется в том, чтобы добиться более высокого уровня целостности данных в стеке.
1. Гарантируйте целостность значений переменных, которые хранятся в стеке, с по­мощью более жестких проверок сигнального слова. Если переменная размещает­ся после буфера в стеке, проверка должна выполняться до использования пере­менной. Частота таких проверок может устанавливаться с помощью использова­ния зависимого от данных анализа.
2. Гарантируйте целостность значений переменных, которые хранятся в стеке, с по­мощью перестройки структуры стека. По возможности локальные "безбуферные" переменные должны размещаться перед переменными, которые используют бу­фер. Более того, поскольку параметры функций сохраняются в стеке «после бу­феров локальных функций (если они есть), то к параметрам функций должен применяться тот же подход. В прологе функции можно зарезервировать допол­нительное пространство в стеке перед данными локальных буферов. Это по­зволяет скопировать значения всех параметров. При каждом использовании параметра в теле функции, его значение можно заменить значением созданной копии. Это решение было внедрено по крайней мере в одном исследователь­ском проекте IBM9.
3. Гарантируйте целостность значений глобальных переменных с помощью контро­лируемого механизма записи. Очень часто значения глобальных переменных ис­кажаются в результате ошибок в программе и/или умышленного повреждения. Контролируемый механизм записи позволяет разместить набор таких перемен­ных в область памяти, доступную только для чтения. При необходимости изме­нения значения переменной из этой области разрешение на доступ к этой области памяти должно быть изменено на "доступно для записи" ("writeable"). После вне­сения необходимых изменений относительно прав доступа, возвращается разре­шение "только для чтения" (read-only). При таком механизме неожиданные по­пытки перезаписи значений защищенных переменных приводят к нарушениям прав доступа к памяти. Для тех переменных, значения которых изменяются один или два раза в ходе выполнения программы, издержки применения контроли­руемого механизма записи незначительны.
В следующих версиях этого компилятора от Microsoft эти идеи нашли примене­ние (частично).
Обзор атаки
Теперь стала очевидной ирония этой атаки: компания Microsoft сама встроила "сеялку" уязвимых мест в свой компилятор добавив функцию, которая была предна­значена для защиты от атак! При этом (что замечательно) шаблоном атаки для взлома этой возможности стал тот же шаблон, для защиты от которого предназна­чалась функция защиты. Суть проблемы заключается в том, что ранее безопасные строковые функции становятся уязвимыми при вызове новой функции. Как ви­дим, это угрожает безопасности программного обеспечения, но защищает от взло­ма программ10.
Два года спустя после публичного обсуждения этого просчета, были созданы по крайней мере две программы атаки, использующие установку при компиляции па­раметра /GS для проведения двухэтапных атак. Как и предсказывалось, механизм защиты использовался в качестве плацдарма для проведения этих атак.

 

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