Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #45, АВГУСТ 2004 г.

Дерни printf за хвост

Крис Касперски aka мыщъх

Спецвыпуск Xakep, номер #045, стр. 045-032-1


Форматированный вывод под прицелом

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

Ошибки форматного вывода не многочисленны и встречаются, главным образом, в UNIX-приложениях, где традиции терминального режима все еще сильны. По некоторым оценкам, в 2002 году было обнаружено порядка 100 уязвимых приложений, а в 2003 – свыше 150! Атаке подверглись сервера баз данных, вращающихся под Oracle, и сервисы UNIX, такие, как syslog или ftp. Ни одной атаки на приложения Windows NT до сих пор не зафиксировано. Это не значит, что Windows NT лучше, просто графический интерфейс не располагает к интенсивному использованию форматного вывода, да и количество консольных утилит под NT очень невелико; в общем, нельзя считать, что он находится в безопасности. И сейчас ты в этом убедишься!

Источники угрозы

Основных источников угрозы три: а) навязывание бажной программе собственных спецификаторов; б) врожденный дисбаланс спецификаторов; в) естественное переполнение буфера-приемника при отсутствии проверки на предельно допустимую длину строки.

Навязывание собственных спецификаторов

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

Рассмотрим следующий пример, к которому мы не раз будет обращаться в дальнейшем.

Пример, подверженный ошибкам переполнения

f(){

char buf_in[32], buf_out[32];

printf("введи имя:"); gets(buf_in);

sprintf(buf_out, "hello, %s!\n", buf_in);

printf(buf_out);

}

Реализация DoS

Для аварийного завершения программы достаточно вызвать нарушение доступа, обратившись к невыделенной, несуществующей или заблокированной ячейке памяти. Это легко. Встретив спецификатор "%s", интерпретатор форматного вывода извлекает из стека парный ему аргумент, трактуя его как указатель на строку. Если же тот отсутствует, интерпретатор хватает первый попавшийся указатель и начинает читать содержимое памяти по этому адресу до тех пор, пока не встретит нуль или не нарвется на запрещенную ячейку. Политика запретов варьируется от одной операционной системы к другой, в частности, при обращении по адресам 00000000h – 0000FFFFh и 7FFF000h – FFFFFFFFh Windows NT всегда возбуждает исключение. Остальные же адреса в зависимости от состояния кучи, стека и статической памяти могут быть как доступными, так и нет.

Откомпилируем пример, приведенный выше, и запустим его на выполнение. Вместо своего имени введем строку "%s". Программа выдаст следующее:

введи имя: %s

hello, hello, %s!\n!"

Чтобы понять, что такое "hello, %s!" и откуда оно здесь взялось, необходимо проанализировать состояние стека на момент вызова printf(buf_out), в чем нам поможет отладчик, например тот, который интегрирован в Microsoft Visual Studio (см. скрин).

Содержание  Вперед на стр. 045-032-2